feat(webhook): add incident.io webhook flow with parallel reconciler path#44
Conversation
Introduces typed incident.io webhook event model (internal/webhook/incident.go) with ParseIncidentEvent for the event-namespaced wrapper key shape, an IncidentHandler interface, and configuration blocks (webhook.incident_io, top-level incident_triage) for the upcoming non-ticketing webhook flow. Pure additive change — no HTTP routes, handlers, or reconciler paths reference the new types yet. Subsequent commits add the handler, the new /webhooks/incident-io route, and the parallel Reconciler.ProcessIncidentEvent path. First commit in a series adding a parallel webhook flow that bypasses the SCM ticketing pipeline (ProcessTicket and the RepoURL gate), intended for use cases where the agent reads context and posts to a chat surface rather than shipping code. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds POST /webhooks/incident-io as a sibling of /webhooks/generic.
Verification, parsing, and dispatch are split between three layers:
- Server-level helpers readRequestBody and verifySvixSignature for
body-read + Svix verification, shared with handleGeneric (svix
branch refactored to use the helpers; HTTP behaviour unchanged).
- handleIncidentIO in incident.go validates the request, parses via
ParseIncidentEvent, and dispatches to the configured IncidentHandler.
- WithIncidentConfig and WithIncidentHandler functional options on
Server, mirroring the WithGenericConfig / WithApprovalHandler pattern.
The IncidentIOConfig runtime type carries the prepared Svix verifier
(its Prepare() method validates auth_mode and pre-constructs the
verifier). Without WithIncidentConfig the new endpoint responds 500
"not configured"; without WithIncidentHandler it responds 500 "no
handler configured" so events are never silently dropped.
Refactor note: the existing handleGeneric svix branch and its body-read
now route through the shared helpers. Behaviour (status codes, response
bodies) is byte-identical to before; the existing TestHandleGeneric_*
tests pass without modification. The structured log fields change
slightly: the previous "invalid generic webhook svix signature" message
becomes "invalid svix signature" with source=generic as a structured
field. Anyone grepping the literal old string will need updating.
Part of the parallel webhook flow that bypasses ProcessTicket and the
RepoURL gate. Subsequent commits add the Reconciler.ProcessIncidentEvent
path and cmd/osmia/main.go wiring.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds Reconciler.ProcessIncidentEvent (internal/controller/incident.go)
as a parallel entry point to ProcessTicket. Launches an agent run for
an incident.io webhook event, bypassing the SCM ticketing pipeline and
the RepoURL gate entirely.
Idempotency keyed on incident_id:event_type so created and
status_updated events for the same incident produce distinct task runs.
Engine selection comes directly from config.IncidentTriage.Engine
(default "claude-code"); per-call AppendSystemPrompt override is applied
on top of baseEngineConfig before BuildExecutionSpec.
Skipped vs ProcessTicket: engine selector chain, cost estimation,
tournament coordinator, pre-start approval gate, episodic memory query,
runNotifyStart, ticketing.MarkInProgress.
Known limitation: when this TaskRun completes, handleJobComplete will
call ticketing.MarkComplete with the incident ID as the TicketID, which
the configured ticketing backend will not recognise. The resulting
"not found" error is logged but non-fatal. The clean fix requires the
use-case-aware completion-handler dispatch that the upcoming abstraction
will provide.
TaskRun ID format is bounded for K8s name/label limits: short event-type
codes ("created"/"updated") plus lowercased incident ID plus millisecond
timestamp.
Part of the parallel webhook flow that bypasses ProcessTicket and the
RepoURL gate. Subsequent commits add cmd/osmia/main.go wiring
(incidentAdapter) and a CHANGELOG entry.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tightens the package-level and function-level doc comments to generally-applicable phrasing. Renames the example skill in test fixtures to "incident-classifier". No behaviour change; tests still pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds an incidentAdapter (parallel to webhookAdapter and approvalAdapter) that satisfies webhook.IncidentHandler by delegating to Reconciler.ProcessIncidentEvent, and registers it in the webhook setup block when cfg.Webhook.IncidentIO is non-nil. The translation from the YAML schema (config.IncidentIOWebhookConfig) to the runtime type (webhook.IncidentIOConfig) mirrors the WithGenericConfig pattern. Without the YAML block the new endpoint and adapter are inert — existing deployments are unaffected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds Unreleased entries for the new POST /webhooks/incident-io endpoint and the structured source field on generic-webhook svix log messages. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds an "incident.io" section to docs/api/webhooks.md mirroring the structure of the existing per-source sections (endpoint, authentication, supported events, dispatch configuration, example payload). Also backfills the previously-undocumented `auth_mode: svix` row in the Generic HTTP authentication table. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (2)
WalkthroughAdds an incident.io webhook receiver: new YAML config and validation, Svix verifier preparation and centralized verification, typed event parsing, HTTP handler POST /webhooks/incident-io, shared request helpers, application wiring, tests/docs/changelog; reconciler entrypoint logs events and currently does not dispatch agent jobs. Changesincident.io Webhook Integration
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
internal/webhook/incident_test.go (1)
90-152: ⚡ Quick winMake the parser happy-path coverage table-driven.
These two success-path tests exercise the same
ParseIncidentEventcontract with different fixtures. Folding them into one table witht.Runwill keep the suite consistent and make the next supported incident event cheaper to add.As per coding guidelines, "Use table-driven tests with subtests (
t.Run) in Go unit tests".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/webhook/incident_test.go` around lines 90 - 152, Combine the two near-duplicate tests TestParseIncidentEvent_CreatedV2 and TestParseIncidentEvent_StatusUpdatedV2 into a single table-driven test (e.g., TestParseIncidentEvent_Table) that iterates over cases containing name, payload (incidentCreatedV2Payload / incidentStatusUpdatedV2Payload) and expected assertions, and use t.Run for each case; call ParseIncidentEvent inside the loop and perform the same assertions previously in each test (EventType, Incident fields, Message/NewStatus/PreviousStatus, and Raw JSON for the created case) while branching on case-specific expectations so adding new fixtures becomes a new table entry.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/webhook/incident.go`:
- Around line 253-261: The EventIncidentStatusUpdatedV2 branch must reject
payloads missing required transition fields; after unmarshalling into
incidentStatusUpdatedBody in the EventIncidentStatusUpdatedV2 case, validate
that w.NewStatus and w.PreviousStatus are present (non-zero/empty) and return a
descriptive error (e.g., fmt.Errorf("decoding %s body: missing new_status" or
"missing previous_status")) instead of proceeding to set
evt.Incident/Message/NewStatus/PreviousStatus; update the case that handles
EventIncidentStatusUpdatedV2 to perform these checks and fail fast on malformed
payloads so only complete incident_status_updated_v2 events are accepted.
---
Nitpick comments:
In `@internal/webhook/incident_test.go`:
- Around line 90-152: Combine the two near-duplicate tests
TestParseIncidentEvent_CreatedV2 and TestParseIncidentEvent_StatusUpdatedV2 into
a single table-driven test (e.g., TestParseIncidentEvent_Table) that iterates
over cases containing name, payload (incidentCreatedV2Payload /
incidentStatusUpdatedV2Payload) and expected assertions, and use t.Run for each
case; call ParseIncidentEvent inside the loop and perform the same assertions
previously in each test (EventType, Incident fields,
Message/NewStatus/PreviousStatus, and Raw JSON for the created case) while
branching on case-specific expectations so adding new fixtures becomes a new
table entry.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: b9a9455e-910a-4cd3-975d-a2fc30cc729c
📒 Files selected for processing (12)
CHANGELOG.mdcmd/osmia/main.godocs/api/webhooks.mdinternal/config/config.gointernal/config/config_test.gointernal/config/validate.gointernal/controller/incident.gointernal/controller/incident_test.gointernal/webhook/generic.gointernal/webhook/incident.gointernal/webhook/incident_test.gointernal/webhook/server.go
…sition fields ParseIncidentEvent now returns a descriptive error when an incident_status_updated_v2 delivery omits new_status or previous_status. Per incident.io's documented schema both fields are required for this event type; rejecting at parse time prevents a triage run from being dispatched without the transition context that the event is supposed to carry. Mirrors the existing empty-incident.id guard for incident_created_v2. Adds two test cases covering the new error paths. Addresses CodeRabbit feedback on PR #44. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Thanks for the review. Quick response on the two findings. ✅ Actionable: reject incomplete
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/webhook/incident.go`:
- Around line 64-80: Prepare() can leave a stale i.svixWebhook verifier when a
later validation fails; clear the previous verifier on error so we fail closed.
In IncidentIOConfig.Prepare(), reset i.svixWebhook = nil on any validation error
return (or clear it at the start of Prepare()) and only set i.svixWebhook to the
new value after svix.NewWebhook succeeds; ensure all error branches (invalid
svix secret, empty auth_mode, unsupported auth_mode) clear the existing verifier
before returning.
- Around line 258-266: The code currently only checks w.NewStatus and
w.PreviousStatus for nil but accepts empty objects; update the validation in the
parsing/validation block that returns IncidentEvent to also ensure the nested
status ID fields are populated (e.g., check w.NewStatus.Id and
w.PreviousStatus.Id or equivalent identifier fields) and return an error with
eventType if either ID is empty/zero; this prevents accepting "{}" objects and
ensures IncidentEvent is only created when both status objects contain valid
IDs.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: ea9a24b0-e131-4eea-b8a7-708a1b1f023e
📒 Files selected for processing (2)
internal/webhook/incident.gointernal/webhook/incident_test.go
✅ Files skipped from review due to trivial changes (1)
- internal/webhook/incident_test.go
Reduces Reconciler.ProcessIncidentEvent to a structured-log no-op that returns nil. The webhook still receives, verifies, parses, and acknowledges every incident.io delivery; the engine selection, TaskRun creation, K8s Job launch, and stream reader are removed and will be restored in a follow-up change paired with the operator-side wiring (classifier skill, helm values that mount it as a ConfigMap and set incident_triage.append_system_prompt). Running the agent without a configured classifier would produce unstructured output and noise on the dispatch surface (e.g. Slack), so deferring dispatch until that wiring exists is the safer state to ship. The receiver-side infrastructure introduced earlier in this branch (types, parser, route, server wiring, config schema, adapter, docs) is unchanged and provides immediate value: incident.io webhook subscriptions can be configured against /webhooks/incident-io and will return 200 OK while we ready the dispatch path. Removed: - defaultIncidentEngine constant - eventTypeSuffix helper - Imports no longer needed (metav1, time, strings, fmt, metrics, taskrun, engine) - Dispatch-exercising tests in internal/controller/incident_test.go (TestProcessIncidentEvent, _Idempotency, _DistinctEventTypes, _DefaultsEngineToClaudeCode, _EngineNotRegistered, _BuildExecutionSpecError, plus the recordingEngine mock) Added: - TestProcessIncidentEvent_DispatchDisabled — regression guard against accidental no-op state and a placeholder until the dispatch tests return alongside the dispatch path Updated: - CHANGELOG entry to describe the receiver-only scope - docs/api/webhooks.md with a "Dispatch is currently disabled" admonition in the incident.io section Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Heads up — scope of this PR has narrowed since opening, with the author's sign-off: Pushed in Why: running the agent without a configured classifier skill produces unstructured output. The classifier skill (a markdown file in a downstream consumer repo) and the helm-side wiring that mounts it as a ConfigMap and sets What's unchanged: receiver-side infrastructure (route, signature verification, parser, types, config schema, adapter, server wiring, docs, CHANGELOG) all stays. That's the immediate value of this PR — incident.io webhook subscriptions can be configured against What's gone: ~330 LOC of dispatch path code in CodeRabbit's previous actionable finding (reject incomplete Ready for human re-review. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
docs/api/webhooks.md (1)
336-338: 💤 Low valueAdd language specifier to fenced code block.
The static analysis tool flagged this code block for missing a language specifier. Adding one improves rendering consistency and silences the linter.
📝 Suggested fix
-``` +```http POST /webhooks/incident-io</details> <details> <summary>🤖 Prompt for AI Agents</summary>Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.In
@docs/api/webhooks.mdaround lines 336 - 338, Add the missing language
specifier to the fenced code block that contains the HTTP example "POST
/webhooks/incident-io" so the linter stops flagging it; replace the
triple-backtick fence before that line with a language-tagged fence (e.g.,rendering in the docs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@docs/api/webhooks.md`:
- Around line 336-338: Add the missing language specifier to the fenced code
block that contains the HTTP example "POST /webhooks/incident-io" so the linter
stops flagging it; replace the triple-backtick fence before that line with a
language-tagged fence (e.g., ```http) and keep the closing triple-backticks
as-is to ensure consistent rendering in the docs.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 44248b3b-fa26-461f-bd76-dd54f079ab4a
📒 Files selected for processing (4)
CHANGELOG.mddocs/api/webhooks.mdinternal/controller/incident.gointernal/controller/incident_test.go
Three small defensive fixes from the latest CodeRabbit review on PR #44: - IncidentIOConfig.Prepare() now clears i.svixWebhook at the start so any error path leaves the config in a fail-closed state. Previously a successful Prepare followed by a failed re-prepare retained the old verifier silently — production only calls Prepare once at startup, but a future hot-reload path must not regress on this. Added TestIncidentIOConfig_PrepareFailsClosed to cover the case. - ParseIncidentEvent's incident_status_updated_v2 branch now also rejects empty status objects ({}). The previous nil-pointer check did not catch a payload like {"new_status": {}}, since an empty JSON object deserialises to a non-nil pointer with zero-value fields. Now also requires non-empty new_status.id and previous_status.id, mirroring the existing Incident.ID guard for incident_created_v2. Two new test cases added. - docs/api/webhooks.md: added the http language specifier to the POST /webhooks/incident-io fenced block for renderer/linter consistency with the other code blocks in the same file. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Applied all three remaining findings in ✅
|
PR #44 (Step 1a) shipped the receiver side of the incident.io webhook flow — endpoint, signature verification, parsing, server wiring, config schema, controller adapter — but deliberately omitted the dispatch body of Reconciler.ProcessIncidentEvent. That deferral was paired with operator-side wiring (a classifier skill + incident_triage.append_system_prompt in helm values), which is now in place separately. This change restores dispatch. Reconciler.ProcessIncidentEvent now: - Keys idempotency on incident_id:event_type so created and status_updated for the same incident produce distinct task runs; duplicate events while the existing run is non-terminal are no-ops. - Selects the engine from config.IncidentTriage.Engine (default claude-code) and returns an error if unregistered. - Builds an engine.Task with the incident name, summary, and permalink, but no RepoURL — so the engine's BuildPrompt no-repo fallback fires and the agent operates from system prompt + skill. - Carries osmia:source:incident-io and osmia:event:<type> labels for downstream observability. - Generates a TaskRun ID of the form tr-incident-<lowercased-id>-<created|updated>-<millis> that stays inside the K8s 63-char limit even with a 26-char ULID. - Layers config.IncidentTriage.AppendSystemPrompt onto the per-call EngineConfig via the override pattern used in the deleted dispatch code. - Drives the standard BuildExecutionSpec -> JobBuilder.Build -> Jobs().Create() path, transitions the TaskRun to Running, and starts the stream reader for claude-code runs. Tests: brought back TestProcessIncidentEvent, _Idempotency, _DistinctEventTypes, _DefaultsEngineToClaudeCode, _EngineNotRegistered, _BuildExecutionSpecError, and the recordingEngine mock that captures AppendSystemPrompt for the per-call override assertion. Removed TestProcessIncidentEvent_DispatchDisabled, which was a regression guard against accidentally returning to the no-op state. Docs: removed the "Dispatch is currently disabled" admonition from docs/api/webhooks.md and replaced the corresponding CHANGELOG entry with a description of the active dispatch behaviour. Known limitation, deferred to the use-case abstraction follow-up: handleJobComplete calls ticketing.MarkComplete with the incident UUID as the TicketID, which the configured ticketing backend will not recognise. The resulting "not found" error is logged but non-fatal. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…45) * feat(controller): restore incident.io dispatch path (Step 1b) PR #44 (Step 1a) shipped the receiver side of the incident.io webhook flow — endpoint, signature verification, parsing, server wiring, config schema, controller adapter — but deliberately omitted the dispatch body of Reconciler.ProcessIncidentEvent. That deferral was paired with operator-side wiring (a classifier skill + incident_triage.append_system_prompt in helm values), which is now in place separately. This change restores dispatch. Reconciler.ProcessIncidentEvent now: - Keys idempotency on incident_id:event_type so created and status_updated for the same incident produce distinct task runs; duplicate events while the existing run is non-terminal are no-ops. - Selects the engine from config.IncidentTriage.Engine (default claude-code) and returns an error if unregistered. - Builds an engine.Task with the incident name, summary, and permalink, but no RepoURL — so the engine's BuildPrompt no-repo fallback fires and the agent operates from system prompt + skill. - Carries osmia:source:incident-io and osmia:event:<type> labels for downstream observability. - Generates a TaskRun ID of the form tr-incident-<lowercased-id>-<created|updated>-<millis> that stays inside the K8s 63-char limit even with a 26-char ULID. - Layers config.IncidentTriage.AppendSystemPrompt onto the per-call EngineConfig via the override pattern used in the deleted dispatch code. - Drives the standard BuildExecutionSpec -> JobBuilder.Build -> Jobs().Create() path, transitions the TaskRun to Running, and starts the stream reader for claude-code runs. Tests: brought back TestProcessIncidentEvent, _Idempotency, _DistinctEventTypes, _DefaultsEngineToClaudeCode, _EngineNotRegistered, _BuildExecutionSpecError, and the recordingEngine mock that captures AppendSystemPrompt for the per-call override assertion. Removed TestProcessIncidentEvent_DispatchDisabled, which was a regression guard against accidentally returning to the no-op state. Docs: removed the "Dispatch is currently disabled" admonition from docs/api/webhooks.md and replaced the corresponding CHANGELOG entry with a description of the active dispatch behaviour. Known limitation, deferred to the use-case abstraction follow-up: handleJobComplete calls ticketing.MarkComplete with the incident UUID as the TicketID, which the configured ticketing backend will not recognise. The resulting "not found" error is logged but non-fatal. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test: add coverage for relaunch-after-terminal idempotency branch ProcessIncidentEvent's idempotency map check only no-ops when the existing TaskRun is non-terminal — once a prior run reaches Succeeded/Failed/TimedOut, a fresh event with the same key should spawn a new TaskRun and Job. The path existed in the dispatch code restored by the previous commit but had no test coverage, raised by CodeRabbit on PR #45. The test transitions the first run Running → Succeeded, then issues a second event for the same incident_id:event_type and asserts a second Job is created. A 2ms sleep separates the calls because the TaskRun ID embeds time.Now().UnixMilli() and the derived K8s Job name would otherwise collide on the same-millisecond path. In production, two webhook deliveries colliding inside a millisecond is implausible. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(webhooks): add authoring guide for the incident classifier skill The incident.io section of docs/api/webhooks.md showed an example incident_triage block that referenced /incident-classifier but never told operators that they have to author the skill themselves. The example skill name and the matching engines.<engine>.skills[] entry were also in separate docs (webhook config in webhooks.md, skill mechanics in plugins/engines.md), forcing operators to derive the wiring from first principles. Adds an "Authoring the classifier skill" subsection that: - States explicitly that Osmia does not ship a classifier — operators provide one to suit their incident taxonomy and response surface. - Shows a complete worked example pairing the incident_triage block with a matching ConfigMap-backed engines.<engine>.skills[] entry, plus the kubectl create configmap invocation. - Lays out the typical structure of a classifier skill (input contract, classifications, output contract, bail-out, operational constraints) without prescribing categories — that's deployment-specific. - Notes how to iterate on the skill without redeploying the controller (update the ConfigMap; next agent Job picks it up). - Cross-references the upstream incident.io API docs for operators who need to derive additional fields from the event payload. No code changes; this is purely operator-facing documentation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(webhooks): use a runnable kubectl example for ConfigMap updates The "Iterating on the skill" subsection used a literal Unicode ellipsis in the inline kubectl invocation as a placeholder. The ellipsis is a real character, not a shell idiom, so anyone copy-pasting the snippet would get a syntax error. Raised by CodeRabbit on PR #45. Replaces the inline backticked one-liner with a proper bash fenced block containing the full create-then-apply pattern, including the namespace and the --from-file= argument. The earlier "Authoring the classifier skill" section already shows the plain `kubectl create configmap` form for first-time creation; this section shows the idempotent re-apply form for subsequent updates. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Adds a new
POST /webhooks/incident-ioendpoint and a parallel reconcilerentry point (
Reconciler.ProcessIncidentEvent) for handling incident.iowebhook events. The new flow bypasses the SCM ticketing pipeline and the
RepoURLgate entirely — the controller does not require a repositoryor merge-request lifecycle for these events. The endpoint is intended
for triage and similar use cases where the agent reads context and
posts to chat rather than shipping code.
The new flow is intentionally implemented in parallel to the existing
ticketing flow rather than refactoring both behind a common interface.
A future change will lift
ProcessTicketandProcessIncidentEventbehind a shared use-case abstraction; this PR introduces the second
consumer first so that abstraction can be designed against two real
consumers rather than speculatively.
Scope of this PR — receiver only; dispatch deferred
This PR ships the receiver half of the new flow:
IncidentHandlerinterfacewebhook.incident_ioandincident_triageblocks)
WithIncidentConfig,WithIncidentHandleroptions,the new route in
NewServer)cmd/osmia/main.goIt does not ship a working dispatch path.
Reconciler.ProcessIncidentEventis a structured-log no-op that returns nil, which causes the webhook
server to respond 200 OK without spawning an agent Job. The dispatch
logic (engine selection,
TaskRuncreation, K8s Job launch, streamreader) arrives in a follow-up change paired with the operator-side
wiring — specifically, a classifier skill and the helm values that
mount it as a ConfigMap and set
incident_triage.append_system_prompt.Running the agent without a configured classifier would produce
unstructured output, so deferring dispatch until that wiring exists
is the safer state to ship.
What's added
POST /webhooks/incident-io— typed handler forpublic_incident.incident_created_v2andpublic_incident.incident_status_updated_v2. Verifies Svixsignatures, parses the event-namespaced wrapper key, and dispatches
to a configured
IncidentHandler.webhook.incident_io(sibling ofwebhook.generic, currentlyauth_mode: svixonly) and a top-levelincident_triageblock carrying the engine name (defaultclaude-code) and anappend_system_promptoverride that, oncedispatch is restored, will be applied per-call to the engine's
EngineConfig.Reconciler.ProcessIncidentEvent— currently a structured-logno-op as described above. Future changes will restore the dispatch
body.
Server:readRequestBodyandverifySvixSignature. The svix branch ofhandleGenericnow usesthese too.
docs/api/webhooks.mdcovering the endpoint, authentication,supported events, dispatch configuration, and example payload, with
a "Dispatch is currently disabled" admonition. Also backfills the
previously-undocumented
auth_mode: svixrow in the Generic HTTPauthentication table.
Behavioural change for existing deployments
Without the new YAML blocks, the new endpoint and reconciler entry
point are inert; existing deployments are unaffected.
One observable change in
handleGeneric's svix branch logging: themessage
"invalid generic webhook svix signature"is now"invalid svix signature"withsource=genericas a structured slogfield, and similarly for the "not prepared" error message. Anyone
alerting on the literal old strings should migrate to the structured
field. HTTP behaviour (status codes, response bodies) is unchanged —
the existing
TestHandleGeneric_*tests pass without modification.Known limitation (future)
When the dispatch path is restored, completed incident
TaskRuns willtrigger
handleJobComplete, which callsticketing.MarkCompletewiththe incident ID as
tr.TicketID. The configured ticketing backendwill not recognise the ID and will log a "not found" error. Logged
but non-fatal. The clean fix requires a use-case-aware completion-
handler dispatch, which the upcoming abstraction will provide. Not
relevant in this PR because the dispatch path is disabled.
Type of Change
Checklist
gofmt,go vet)and config parsing / validation; the controller-side dispatch
tests will be added alongside the dispatch path itself in the
follow-up change
go test ./...)docs/api/webhooks.mdRelated Issues
N/A
Summary by CodeRabbit
New Features
Improvements
Documentation