dispatcher creates a DUPLICATE system webhook on every boot (Forgejo admin-hooks LIST returns [] while the hooks exist) #63

Closed
opened 2026-07-02 01:31:08 +00:00 by plat · 3 comments
Owner

Found while validating a fresh wb-gate germination (plat.local, Forgejo 15.0.0+gitea-1.22.0, agents 0.7.5). ensureWebhook boot idempotency is list-based: listAdminHooks() → find by URL → create if absent (agents src/dispatcher.ts ~3162). On this Forgejo, GET /api/v1/admin/hooks returns [] under EVERY variant (?type=system, ?type=default, none) even though the hook EXISTS and FIRES — GET /api/v1/admin/hooks/1 returns it fine. So mine is never found and every dispatcher boot creates another system hook. PROVEN LIVE: one rollout restart → hooks id 1 AND id 3, same dispatcher URL, both active → every Forgejo event delivers twice (doubled runs/noise; RBAC-5 re-apply can never patch either). Fix direction (version-agnostic, uses the endpoint that demonstrably works): persist the created hook id in dispatcher state (/data survives restarts); on boot GET /admin/hooks/{id} directly → patch (re-applies the sealed HMAC secret per RBAC-5) → create+store only on 404; best-effort delete any listed hook with our URL that is not state.webhookId. Interim operator remediation: probe ids and DELETE /api/v1/admin/hooks/<dup>.

Found while validating a fresh wb-gate germination (plat.local, Forgejo 15.0.0+gitea-1.22.0, agents 0.7.5). `ensureWebhook` boot idempotency is list-based: `listAdminHooks()` → find by URL → create if absent (agents src/dispatcher.ts ~3162). On this Forgejo, `GET /api/v1/admin/hooks` returns `[]` under EVERY variant (`?type=system`, `?type=default`, none) even though the hook EXISTS and FIRES — `GET /api/v1/admin/hooks/1` returns it fine. So `mine` is never found and every dispatcher boot creates another system hook. PROVEN LIVE: one `rollout restart` → hooks id 1 AND id 3, same dispatcher URL, both active → every Forgejo event delivers twice (doubled runs/noise; RBAC-5 re-apply can never patch either). Fix direction (version-agnostic, uses the endpoint that demonstrably works): persist the created hook id in dispatcher state (/data survives restarts); on boot GET `/admin/hooks/{id}` directly → patch (re-applies the sealed HMAC secret per RBAC-5) → create+store only on 404; best-effort delete any listed hook with our URL that is not `state.webhookId`. Interim operator remediation: probe ids and `DELETE /api/v1/admin/hooks/<dup>`.
Author
Owner

Fix open: plat/agents#10 — webhook idempotency by persisted id (verify via GET /admin/hooks/{id}, which answers truthfully; adopt-by-list when the list works; best-effort duplicate reap). Regression-tested (two-boots-with-lying-list) and client paths validated against the live Forgejo 15 on plat.local. The duplicate hook on plat.local was removed by hand (DELETE /admin/hooks/3).

Fix open: plat/agents#10 — webhook idempotency by persisted id (verify via `GET /admin/hooks/{id}`, which answers truthfully; adopt-by-list when the list works; best-effort duplicate reap). Regression-tested (two-boots-with-lying-list) and client paths validated against the live Forgejo 15 on plat.local. The duplicate hook on plat.local was removed by hand (`DELETE /admin/hooks/3`).
Author
Owner

Prod (0.7.6 rollout) follow-through: first boot with empty dispatcher state + the lying LIST took the create path as designed → new owned hook id 182 alongside the legacy id 1 → double-delivery for ~9h. Applied the documented remediation: deleted the unowned id 1; id 182 verified sole + persisted in state (future boots take the verify path — confirmed on val076). Fresh germinations are immune (create-once + persist). Upstream note: GET /api/v1/admin/hooks returning [] while hooks exist reproduces on Forgejo 15.0.0+gitea-1.22.0 — worth an upstream report; the dispatcher no longer depends on it either way.

Prod (0.7.6 rollout) follow-through: first boot with empty dispatcher state + the lying LIST took the create path as designed → new owned hook id 182 alongside the legacy id 1 → double-delivery for ~9h. Applied the documented remediation: deleted the unowned id 1; id 182 verified sole + persisted in state (future boots take the verify path — confirmed on val076). Fresh germinations are immune (create-once + persist). Upstream note: `GET /api/v1/admin/hooks` returning [] while hooks exist reproduces on Forgejo 15.0.0+gitea-1.22.0 — worth an upstream report; the dispatcher no longer depends on it either way.
Author
Owner

Fixed in agents v0.7.6 (plat/agents#10 → release #13). Idempotency by persisted hook id; validated on val076 (restart → still one hook, verify path) and remediated live on prod (deleted legacy id 1; owned id 182 sole). Closing.

Fixed in agents v0.7.6 (plat/agents#10 → release #13). Idempotency by persisted hook id; validated on val076 (restart → still one hook, verify path) and remediated live on prod (deleted legacy id 1; owned id 182 sole). Closing.
plat closed this issue 2026-07-02 13:06:30 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
open-platform/mitosis#63
No description provided.