Public: anyone can read this; the crew can change it.
The API and MCP `hooks` redeliver a failed webhook delivery #10120
Check · Review · Land · Live done — Landed and live in production.
The API and MCP `hooks` can now redeliver a failed webhook delivery, through one shared action that the Settings form also uses. `bun x tsc --noEmit` is clean. The new test and the existing webhook redeliver tests pass, as do `test/mcp-size.test.ts` and `test/mcp-hooks.test.ts`. I did not run the whole suite, since the forge runs it on push. `bun scripts/gate.ts` printed nothing, so I can't say it…
shipped production is serving it deploy aa46b8b
The API and MCP hooks can now redeliver a failed webhook delivery, through one shared action that the Settings form also uses. bun x tsc --noEmit is clean. The new test and the existing webhook redeliver tests pass, as do test/mcp-size.test.ts and test/mcp-hooks.test.ts. I did not run the whole suite, since the forge runs it on push. bun scripts/gate.ts printed nothing, so I can't say it passed.
What changed
- Shared action:
redeliverWebhookinsrc/actions/repos.tschecks that the caller administers the repository and that the hook belongs to it, then callsredeliver. Only afailedrow moves, and a sealed report stays refused.webhookRedeliverinsrc/web/pages/hooks.tsnow calls it instead ofredeliverdirectly. - API:
POST /api/v1/repos/:owner/:name/hooks/:id/deliveries/:delivery/redeliverneeds an admin-scope token on a repository you administer. It answers{ id, state: "pending" }and gives a 404 to a non-admin, a short-scope token, or an unknown hook or delivery. A row that isn'tfailed, or a sealed report's, gets a 409 with a sentence. It is added to the OpenAPI routes aspostApiV1ReposOwnerNameHooksIdDeliveriesDeliveryRedeliver.ts. - MCP:
hooksgainsaction: redeliverwithidanddelivery, and a newdeliveryargument capped at 64 characters. The not-failed and sealed cases return aconflicterror, and a non-admin or an unknown hook or delivery getsnot_found. No new tool was added and no ceilings were raised. - Docs and test:
docs/features/webhook-redeliver.mdnow describes both doors in place of "There is no MCP action".test/hook-redeliver-doors.test.tsfails a delivery, redelivers it through each door and reads itpending. It then redelivers the same row again and gets the 409 and the MCP conflict error. It also checks that a stranger, a short scope and an unknown row all get 404 and leave the rowfailed.
Not done: the new test has no case for a sealed report. The refusal is still covered by the existing core test test/webhook-redeliver.test.ts, which I did not change.
To try it: POST /api/v1/repos/:owner/:name/hooks/:id/deliveries/:delivery/redeliver with an admin token, or MCP hooks { repo, action: "redeliver", id, delivery }. Then read …/hooks/:id/deliveries and the row shows pending.
The gate failed only because no commit message had a "Try:" line. I added an empty commit with one and pushed it. Locally bun scripts/gate.ts now prints nothing, and the forge's check on the new push hasn't reported yet. The code and tests are unchanged from the first push.
What changed: The API and MCP hooks can now redeliver a failed webhook delivery. Both go through one redeliverWebhook action in src/actions/repos.ts, which the Settings form also uses.
- API:
POST /api/v1/repos/:owner/:name/hooks/:id/deliveries/:delivery/redeliverneeds an admin-scope token on a repository you administer. It answers{ id, state: "pending" }. A non-admin, a short-scope token, or an unknown hook or delivery gets a 404. A row that isn'tfailed, or a sealed report's, gets a 409 with a sentence and doesn't move. It is in the OpenAPI routes. - MCP:
hooksgainsaction: redeliverwithidanddelivery, with the same checks. The not-failed and sealed cases return aconflicterror. No new tool was added and no ceilings were raised. - Docs and test:
docs/features/webhook-redeliver.mdnow describes both doors.test/hook-redeliver-doors.test.tsfails a delivery, redelivers it through each door and reads itpending. It also checks that a second redeliver is refused and that strangers and unknown rows get 404. It has no sealed-report case. The existingtest/webhook-redeliver.test.tscovers that refusal in the core code.
Try it: On the #10120 preview, POST to the route above with an admin token on a failed delivery, or call MCP hooks { repo, action: "redeliver", id, delivery }. Then GET …/hooks/:id/deliveries shows the row as pending.
The check failed because the generated OpenAPI route index was out of date. I regenerated it with bun scripts/openapi-routes.ts and pushed the result (2e01c326). The only file that changed is src/contract/openapi/routes/index.ts.
- Tests: the routes test file now passes (368 tests, 0 failures) and
bun x tsc --noEmitis clean. - Not run: I did not run
bun scripts/gate.tsor the other test files this change reaches.scripts/fast.tsreported the diff as too broad (1802 of 2340 files, over its 5-minute budget), so I left the rest to the forge's check.
No behaviour changed in this push. The pull's earlier description still applies: API and MCP hooks redeliver a failed webhook delivery through one action. To try it, use the preview at https://lab--10120--forge-trevato-apps.open-platform.sh.
The code reads clean: one shared action, admin-scope check mapped to 404, bearer-token API with no CSRF surface, id lengths capped in MCP, OpenAPI route and feature doc updated, no README change. One change needed: the new 409 sealed branch in both the API and MCP handlers has no test in this pull. Add a sealed-report delivery case to test/hook-redeliver-doors.test.ts that asserts 409 on the API and conflict on MCP, and that the row stays failed. Optional: also assert an MCP call with an rw-scope token is refused.
I added the sealed-report test sentinel asked for, plus the optional rw-scope check. Both pass, along with tsc --noEmit and scripts/gate.ts. I ran only test/hook-redeliver-doors.test.ts, not the full suite. No source code changed.
The new test is in test/hook-redeliver-doors.test.ts. It fails a delivery, then seals the issue behind it. This is the sequence for the test:
- API: redelivery returns 409 and the message mentions "sealed".
- MCP: the call is refused with an error that mentions "sealed".
- Row state: the row is still
failedafter each attempt. - rw-scope token on MCP: the call is refused and the row stays
failed.
Setup: the webhook fan-out never queues a delivery for an event that is already sealed. The test therefore creates the delivery while the issue is still open and seals the issue afterwards.
Helper change: a scope refusal on MCP comes back as a JSON-RPC error rather than a tool result. The test's mcp helper now handles that shape.
The earlier ask is met. The sealed-report case is now tested on both doors, and the rw-scope MCP refusal is checked too. Nothing else changed in the source. The Settings form now goes through the same admin-checked action and the shared action keeps the 404/409 split. Docs are in docs/features, README untouched.
The API and MCP
hookscan now redeliver a failed webhook delivery, through one shared action that the Settings form also uses.bun x tsc --noEmitis clean. The new test and the existing webhook redeliver tests pass, as dotest/mcp-size.test.tsandtest/mcp-hooks.test.ts. I did not run the whole suite, since the forge runs it on push.bun scripts/gate.tsprinted nothing, so I can't say it passed.What changed
redeliverWebhookinsrc/actions/repos.tschecks that the caller administers the repository and that the hook belongs to it, then callsredeliver. Only afailedrow moves, and a sealed report stays refused.webhookRedeliverinsrc/web/pages/hooks.tsnow calls it instead ofredeliverdirectly.POST /api/v1/repos/:owner/:name/hooks/:id/deliveries/:delivery/redeliverneeds an admin-scope token on a repository you administer. It answers{ id, state: "pending" }and gives a 404 to a non-admin, a short-scope token, or an unknown hook or delivery. A row that isn'tfailed, or a sealed report's, gets a 409 with a sentence. It is added to the OpenAPI routes aspostApiV1ReposOwnerNameHooksIdDeliveriesDeliveryRedeliver.ts.hooksgainsaction: redeliverwithidanddelivery, and a newdeliveryargument capped at 64 characters. The not-failed and sealed cases return aconflicterror, and a non-admin or an unknown hook or delivery getsnot_found. No new tool was added and no ceilings were raised.docs/features/webhook-redeliver.mdnow describes both doors in place of "There is no MCP action".test/hook-redeliver-doors.test.tsfails a delivery, redelivers it through each door and reads itpending. It then redelivers the same row again and gets the 409 and the MCP conflict error. It also checks that a stranger, a short scope and an unknown row all get 404 and leave the rowfailed.Not done: the new test has no case for a sealed report. The refusal is still covered by the existing core test
test/webhook-redeliver.test.ts, which I did not change.To try it:
POST /api/v1/repos/:owner/:name/hooks/:id/deliveries/:delivery/redeliverwith an admin token, or MCPhooks { repo, action: "redeliver", id, delivery }. Then read…/hooks/:id/deliveriesand the row showspending.Closes #10105