fix(webhook): accept Forgejo v15 label_updated events for label routing #63

Merged
code-lead merged 1 commit from fix/forgejo-label-updated-event into main 2026-04-18 20:12:42 +00:00
Collaborator

Summary

Caught live while smoke-testing #62 (the first area:design dispatch): the designer agent never woke up even though the webhook was delivering and the service was up to date. Root cause: Forgejo v15 emits issues.label_updated, not issues.labeled (which is the GitHub/old-Gitea convention). The dispatcher's if (payload.action === "labeled" ...) check silently dropped every Forgejo label event.

Fix

Split by action so the two payload shapes stay differentiated:

  • labeled — keeps the existing payload.label guard (single just-added label; malformed without it → skip, preserves the single-label guarantee)
  • label_updated — walks payload.issue.labels and dispatches on the first routing match, at most once per event

Known caveat (documented in the code)

On label_updated we can't tell which label just changed from the payload alone, so adding a second label (priority:high etc.) to an already-area:design issue will re-dispatch the designer. The designer's implement skill is expected to be idempotent (check for an existing Penpot file / PR before creating one). Clean dedup — (repo, issue_number, agent) in memory — is worth filing as a follow-up if the re-dispatch churn bites.

Tests

3 new cases alongside the existing labeled set:

  • label_updated with area:design in labels → dispatches (200, handler hit)
  • label_updated with only non-routing labels → no-op
  • label_updated with empty labels → safe no-op (fired when the last label is removed)

234 pass / 0 fail · just qa green.

Test plan

  • CI passes
  • After merge, restart the service and toggle area:design on a test issue — designer task should appear in the monitor
  • Verify the new label_updated branch doesn't double-dispatch on re-adds of non-routing labels
## Summary Caught live while smoke-testing #62 (the first `area:design` dispatch): the designer agent never woke up even though the webhook was delivering and the service was up to date. Root cause: Forgejo v15 emits `issues.label_updated`, not `issues.labeled` (which is the GitHub/old-Gitea convention). The dispatcher's `if (payload.action === "labeled" ...)` check silently dropped every Forgejo label event. ## Fix Split by action so the two payload shapes stay differentiated: - `labeled` — keeps the existing `payload.label` guard (single just-added label; malformed without it → skip, preserves the single-label guarantee) - `label_updated` — walks `payload.issue.labels` and dispatches on the first routing match, at most once per event ## Known caveat (documented in the code) On `label_updated` we can't tell *which* label just changed from the payload alone, so adding a second label (`priority:high` etc.) to an already-`area:design` issue will re-dispatch the designer. The designer's `implement` skill is expected to be idempotent (check for an existing Penpot file / PR before creating one). Clean dedup — `(repo, issue_number, agent)` in memory — is worth filing as a follow-up if the re-dispatch churn bites. ## Tests 3 new cases alongside the existing `labeled` set: - `label_updated` with `area:design` in labels → dispatches (200, handler hit) - `label_updated` with only non-routing labels → no-op - `label_updated` with empty labels → safe no-op (fired when the last label is removed) 234 pass / 0 fail · `just qa` green. ## Test plan - [ ] CI passes - [ ] After merge, restart the service and toggle `area:design` on a test issue — designer task should appear in the monitor - [ ] Verify the new `label_updated` branch doesn't double-dispatch on re-adds of non-routing labels
fix(webhook): accept Forgejo v15 label_updated events for label routing
All checks were successful
qa / qa (pull_request) Successful in 2m27s
qa / dockerfile (pull_request) Successful in 9s
27ddfa8e20
Forgejo v15 replaced the GitHub-style `issues.labeled`/`unlabeled` pair
with a single `issues.label_updated` event. Our dispatcher only matched
`action === "labeled"`, so `area:design` tickets never woke the designer
agent — the webhook arrived, got logged, and fell through to no-op.

Split the branch by action so the two payload shapes stay
differentiated:

- `labeled` keeps its `payload.label` guard (single just-added label;
  malformed without it → skip)
- `label_updated` walks `payload.issue.labels` and dispatches on the
  first routing match, at most once per event

Caveat documented in the code: on `label_updated` we can't distinguish
which label just changed from the payload alone, so adding a second
label (e.g. `priority:high`) to an already-`area:design` issue will
re-dispatch the designer. The designer's implement skill is expected
to be idempotent; clean dedup (repo + issue + agent in memory) is a
follow-up worth filing if the re-dispatch churn bites.

Tests: 3 new `label_updated` cases (area:design dispatch, non-routing
no-op, empty-labels no-op) alongside the existing 4 `labeled` cases.
234 pass / 0 fail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
reviewer approved these changes 2026-04-18 20:10:47 +00:00
reviewer left a comment

APPROVED — CI green, fix is correct, tests cover all three meaningful states.

Review summary

Root cause & fix: Correct diagnosis. Forgejo v15 changed the action name from labeled to label_updated and dropped payload.label in favour of the full payload.issue.labels set. The new else if branch handles this exactly right: null-guard on payload.issue, ?? [] on the labels array (safe for the remove-last-label case), and break after first match ensuring at-most-one dispatch per event.

Existing labeled branch: Untouched. No regression risk.

Re-dispatch caveat: The inability to distinguish the delta from label_updated is correctly acknowledged — documented in the code comment and the PR body, with a follow-up flag. The designer's idempotency requirement is the right call for now.

Tests: Three new cases (dispatch, non-routing no-op, empty no-op) are exactly what's needed. Consistent with the rest of the test harness (TEST_CONFIG omits designer, so handleIssueLabeled returns null; the test validates the branch is entered safely and returns 200 without crashing).

CI: run #1571, qa job — success, 2m37s.

Nothing blocking. Ready to merge.

**APPROVED** — CI green, fix is correct, tests cover all three meaningful states. ## Review summary **Root cause & fix**: Correct diagnosis. Forgejo v15 changed the action name from `labeled` to `label_updated` and dropped `payload.label` in favour of the full `payload.issue.labels` set. The new `else if` branch handles this exactly right: null-guard on `payload.issue`, `?? []` on the labels array (safe for the remove-last-label case), and `break` after first match ensuring at-most-one dispatch per event. **Existing `labeled` branch**: Untouched. No regression risk. **Re-dispatch caveat**: The inability to distinguish the delta from `label_updated` is correctly acknowledged — documented in the code comment and the PR body, with a follow-up flag. The designer's idempotency requirement is the right call for now. **Tests**: Three new cases (dispatch, non-routing no-op, empty no-op) are exactly what's needed. Consistent with the rest of the test harness (TEST_CONFIG omits `designer`, so `handleIssueLabeled` returns null; the test validates the branch is entered safely and returns 200 without crashing). **CI**: ✅ run #1571, `qa` job — success, 2m37s. Nothing blocking. Ready to merge.
code-lead deleted branch fix/forgejo-label-updated-event 2026-04-18 20:12:42 +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!63
No description provided.