Skip to content

Add webhookUrl terminal callback to /repo/agent and make request state durable#1482

Merged
Evanfeenstra merged 1 commit into
mainfrom
repo-agent-webhook-url
Jul 24, 2026
Merged

Add webhookUrl terminal callback to /repo/agent and make request state durable#1482
Evanfeenstra merged 1 commit into
mainfrom
repo-agent-webhook-url

Conversation

@Evanfeenstra

Copy link
Copy Markdown
Collaborator

What

Long-running /repo/agent consumers (e.g. Hive's workflow_explorer_agent) currently pull results via GET /progress from inside a duration-capped lambda — runs longer than the caller's poll budget lose their result even though the swarm finished fine. This flips delivery from pull to push and hardens the request-state layer.

webhookUrl terminal callback

POST /repo/agent (non-streaming path) accepts an optional webhookUrl in the body. When the run reaches a terminal state, the swarm POSTs:

  • success: { request_id, status: "completed", result: { success, final_answer, content, tool_use, usage, sessionId } }
  • failure: { request_id, status: "failed", error } (aborts arrive as error: "aborted")

Same shape as GET /progress, with request_id added for correlation. Delivery retries 3× (0s / +5s / +30s, 15s timeout each) and is strictly best-effort — it can never affect the run's recorded status, and /progress polling still works as a fallback. The URL is opaque to the swarm; callers embed their own auth (per-run token / JWT) in it.

Completion-race fix

bus.emit on both terminal paths is now isolated in try/catch, so a post-completion side effect throwing can no longer fall into the promise .catch() and overwrite a just-written completed status with failed.

Durable request state (reqs.ts)

  • checkReq reads the .reqs/<id>.json file directly instead of gating on the in-memory META map — a process restart no longer 404s a completed run whose file survived on disk.
  • finishReq/failReq write to disk even when the entry was evicted past MAX_REQS, so a long run outliving eviction still records its terminal state.

Testing

  • tsc --noEmit clean.
  • Two-process smoke test of the restart scenario: process 1 startReq + finishReq, a fresh process (empty META) checkReqs the id — previously undefined (404), now returns the completed result.

🤖 Generated with Claude Code

…e durable

- POST /repo/agent (non-streaming) accepts an optional webhookUrl; the
  terminal payload ({request_id, status, result|error} — same shape as
  GET /progress) is POSTed to it with retries when the run completes or
  fails, so callers no longer need a poll loop alive for the life of
  the run. Best-effort: delivery can never affect the recorded status,
  and /progress remains the fallback.
- Isolate bus.emit on both terminal paths so a post-completion side
  effect throwing can no longer flip a just-written "completed" status
  to "failed" via the promise .catch.
- reqs.ts: checkReq now reads the .reqs/<id>.json file directly instead
  of gating on the in-memory META map, so a process restart no longer
  404s a completed run whose file survived; finishReq/failReq write to
  disk even when the entry was evicted past MAX_REQS.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Evanfeenstra
Evanfeenstra merged commit c5164b4 into main Jul 24, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant