Skip to content

fix(server): make GitHub webhook registration idempotent (#68) - #71

Merged
dvcdsys merged 2 commits into
developfrom
fix/webhook-idempotent-registration
Jun 4, 2026
Merged

fix(server): make GitHub webhook registration idempotent (#68)#71
dvcdsys merged 2 commits into
developfrom
fix/webhook-idempotent-registration

Conversation

@dvcdsys

@dvcdsys dvcdsys commented Jun 4, 2026

Copy link
Copy Markdown
Owner

Fixes #68.

Problem

GitHub webhook auto-registration was not idempotent. Both registration paths created hooks blindly:

  • tryAutoRegisterWebhook always called CreateWebhook (a bare POST, no existence check).
  • reconcileOne only PATCHed when it had a stored WebhookID; otherwise it created.

There was no ListWebhooks at all, so neither path could match on config.url. Every re-add / reindex / restart where the stored id was lost POSTed another identical hook → a repo accumulated multiple hooks with the same delivery URL, and GitHub fanned every push out N× (N× redundant clone/reindex + embedding-API calls). The docs even claimed dedup happened — it didn't.

Fix

server/internal/githubapi/githubapi.go

  • Add config.url to HookResponse (new HookConfig).
  • Add ListWebhooks (GET …/hooks, paginated).
  • Add EnsureWebhook — idempotent entry point: lists hooks, matches on delivery URL, PATCHes the existing hook (refreshing secret/events) instead of creating, prunes any extra same-URL duplicates, and only POSTs when none match. Returns a created bool, handles the list→patch race (404 → create), and refuses to reuse a hook for a different URL.

server/internal/httpapi/gitrepos.go — auto-register now goes through EnsureWebhook.

server/internal/tunnels/reconciler.go — the create fallback now goes through EnsureWebhook (interface updated), so a repo whose WebhookID was lost reuses its existing hook instead of duplicating. A reregister sweep never leaves old + new hooks side by side.

Tests & docs

  • 4 new githubapi tests (create / reuse / prune 3 duplicates → 1 / ignore non-matching URL) against a stateful in-memory hooks server.
  • New reconciler test: no stored id + existing matching hook → reused, never duplicated.
  • doc/WEBHOOKS.md §4 and §6 updated to describe the real idempotent behavior.

go test ./... and go vet ./... are green. Changes scoped to server/ and doc/.

Out of scope

Deliberately did not auto-delete hooks pointing at a different (stale) URL — that's the issue's "optional" cleanup and silently removing differently-targeted hooks is risky. Same-URL duplicate pruning (the reported bug) is handled.

🤖 Generated with Claude Code

dvcdsys and others added 2 commits June 4, 2026 19:49
Auto-registration always POSTed a new hook instead of checking whether
one with the same delivery URL already existed, so re-adds, reindexes,
and restarts accumulated duplicate hooks on a repo — GitHub then fanned
every push out N times.

Add githubapi.ListWebhooks + EnsureWebhook: list existing hooks, match on
config.url, PATCH the match (and prune same-URL duplicates) instead of
creating, and only POST when none match. Wire both registration paths
(tryAutoRegisterWebhook, reconciler.reconcileOne) through it. The
reconciler now reuses an existing hook even when the stored WebhookID was
lost, so a reregister sweep never leaves old + new hooks side by side.

Covered by new githubapi tests (create / reuse / prune-duplicates /
ignore-non-matching-URL) and a reconciler reuse test. Docs updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…doc caveat

Address PR review:
- When the matched hook is deleted between list and PATCH (404), the
  create-replacement branch now also prunes any other same-URL
  duplicates we listed, instead of leaving them behind.
- WEBHOOKS.md §6: note that a reconcile sweep does NOT prune pre-existing
  same-URL duplicates when the stored hook id is still valid (it PATCHes
  that one and returns); re-adding the repo collapses them via
  EnsureWebhook.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dvcdsys
dvcdsys merged commit 209030c into develop Jun 4, 2026
1 check passed
@dvcdsys
dvcdsys deleted the fix/webhook-idempotent-registration branch June 4, 2026 18:58
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