feat(watched-repos): F5 — watch/unwatch + automatic webhook lifecycle #503

Merged
code-lead merged 1 commit from dev/486 into main 2026-04-28 10:52:13 +00:00
Collaborator

Adds POST/DELETE/PATCH /watched-repos endpoints that register and remove forge webhooks automatically when an operator watches or unwatches a repo.

Closes #486

Test plan

  • POST /watched-repos with { owner, name } → 201, row in watched_repos, forge webhook created; webhook URL is <public_base_url>/webhooks/<forge_type>.
  • POST /watched-repos with no active forge → 412.
  • POST /watched-repos for an already-watched repo → 409.
  • DELETE /watched-repos/:owner/:name → 204, forge webhook deleted, row removed. 404 from forge still removes the row.
  • PATCH /watched-repos/:owner/:name { enabled: false } → 200, forge webhook deactivated.
  • GET /watched-repos → 200, lists all rows.
  • public_base_url absent with OAuth clients configured → startup error.
  • All 15 new unit tests pass; full suite (2288 tests) clean.
Adds `POST/DELETE/PATCH /watched-repos` endpoints that register and remove forge webhooks automatically when an operator watches or unwatches a repo. Closes #486 ## Test plan - `POST /watched-repos` with `{ owner, name }` → 201, row in `watched_repos`, forge webhook created; webhook URL is `<public_base_url>/webhooks/<forge_type>`. - `POST /watched-repos` with no active forge → 412. - `POST /watched-repos` for an already-watched repo → 409. - `DELETE /watched-repos/:owner/:name` → 204, forge webhook deleted, row removed. 404 from forge still removes the row. - `PATCH /watched-repos/:owner/:name` `{ enabled: false }` → 200, forge webhook deactivated. - `GET /watched-repos` → 200, lists all rows. - `public_base_url` absent with OAuth clients configured → startup error. - All 15 new unit tests pass; full suite (2288 tests) clean.
feat(watched-repos): F5 — watch/unwatch + automatic webhook lifecycle
All checks were successful
qa / qa (pull_request) Successful in 9m6s
qa / dockerfile (pull_request) Successful in 11s
b80f02479b
Adds POST/DELETE/PATCH /watched-repos endpoints that create, delete, and
toggle forge webhooks automatically when an operator watches or unwatches
a repo. Introduces operator_oauth_tokens and watched_repos SQLite tables,
extends ForgePort with createWebhook/deleteWebhook/editWebhook/listWebhooks
across all three adapters (Forgejo, GitHub, GitLab), and adds public_base_url
config key with startup validation when OAuth clients are configured.

Closes #486

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
dev requested review from reviewer 2026-04-28 00:20:54 +00:00
reviewer approved these changes 2026-04-28 00:25:26 +00:00
reviewer left a comment

CI green. All 8 ACs verified: watched_repos schema correct, randomBytes(32).toString("hex") 64-char secret, per-forge webhook lifecycle (Forgejo/GitHub/GitLab), idempotency 409, row persists with webhook_id=null on forge failure, public_base_url startup guard, guardMutating on all routes, 15 tests covering 412 guards + per-forge round-trips + validation.

Nit: DELETE/PATCH apply checkActiveForge(), so they return 412 if no forge is active — AC says "unwatch rows from any forge they have an OAuth row for." The stated cleanup use case (switching forges) always has an active forge set, so this doesn't block the workflow in practice.

CI green. All 8 ACs verified: `watched_repos` schema correct, `randomBytes(32).toString("hex")` 64-char secret, per-forge webhook lifecycle (Forgejo/GitHub/GitLab), idempotency 409, row persists with `webhook_id=null` on forge failure, `public_base_url` startup guard, `guardMutating` on all routes, 15 tests covering 412 guards + per-forge round-trips + validation. Nit: DELETE/PATCH apply `checkActiveForge()`, so they return 412 if no forge is active — AC says "unwatch rows from any forge they have an OAuth row for." The stated cleanup use case (switching forges) always has an active forge set, so this doesn't block the workflow in practice.
dev force-pushed dev/486 from b80f02479b
All checks were successful
qa / qa (pull_request) Successful in 9m6s
qa / dockerfile (pull_request) Successful in 11s
to 83142e117f
All checks were successful
qa / qa (pull_request) Successful in 8m37s
qa / dockerfile (pull_request) Successful in 9s
2026-04-28 00:43:08 +00:00
Compare
dev force-pushed dev/486 from 83142e117f
All checks were successful
qa / qa (pull_request) Successful in 8m37s
qa / dockerfile (pull_request) Successful in 9s
to 5c4ec3b067
Some checks are pending
qa / qa (pull_request) Waiting to run
qa / dockerfile (pull_request) Waiting to run
2026-04-28 06:58:50 +00:00
Compare
dev force-pushed dev/486 from 5c4ec3b067
Some checks are pending
qa / qa (pull_request) Waiting to run
qa / dockerfile (pull_request) Waiting to run
to 8318132d03
Some checks are pending
qa / qa (pull_request) Waiting to run
qa / dockerfile (pull_request) Waiting to run
2026-04-28 07:13:49 +00:00
Compare
dev force-pushed dev/486 from 8318132d03
Some checks are pending
qa / qa (pull_request) Waiting to run
qa / dockerfile (pull_request) Waiting to run
to d2dce5b88c
All checks were successful
qa / qa (pull_request) Successful in 11m50s
qa / dockerfile (pull_request) Successful in 12s
2026-04-28 07:41:54 +00:00
Compare
dev force-pushed dev/486 from d2dce5b88c
All checks were successful
qa / qa (pull_request) Successful in 11m50s
qa / dockerfile (pull_request) Successful in 12s
to 437fa6d5f0
Some checks are pending
qa / qa (pull_request) Waiting to run
qa / dockerfile (pull_request) Waiting to run
2026-04-28 08:43:38 +00:00
Compare
dev force-pushed dev/486 from 437fa6d5f0
Some checks are pending
qa / qa (pull_request) Waiting to run
qa / dockerfile (pull_request) Waiting to run
to 1d45db6621
All checks were successful
qa / qa (pull_request) Successful in 11m47s
qa / dockerfile (pull_request) Successful in 14s
2026-04-28 08:56:27 +00:00
Compare
dev force-pushed dev/486 from 1d45db6621
All checks were successful
qa / qa (pull_request) Successful in 11m47s
qa / dockerfile (pull_request) Successful in 14s
to 1fdbbabb17
Some checks failed
qa / qa (pull_request) Has been cancelled
qa / dockerfile (pull_request) Has been cancelled
2026-04-28 09:31:45 +00:00
Compare
dev force-pushed dev/486 from 1fdbbabb17
Some checks failed
qa / qa (pull_request) Has been cancelled
qa / dockerfile (pull_request) Has been cancelled
to 2bfa2f6adb
All checks were successful
qa / qa (pull_request) Successful in 11m54s
qa / dockerfile (pull_request) Successful in 14s
2026-04-28 09:42:48 +00:00
Compare
code-lead deleted branch dev/486 2026-04-28 10:52:14 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
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
charles/claude-hooks!503
No description provided.