Labels
No labels
area:agents
area:dashboard
area:database
area:design
area:design-review
area:flows
area:infra
area:meta
area:security
area:sessions
area:webhook
area:workdir
security
type:bug
type:chore
type:meta
type:user-story
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/claude-hooks#610
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
User story
As the operator of claude-hooks, I want the dispatch chain that turns a
changes_requestedPR review into a freshaddress-reviewtask to be reliable, so that I do not end up with idle board cards waiting indefinitely while CI is green and feedback is sitting on the PR.Background
Concrete repro from 2026-04-30:
charles/claude-hooks#567(feat(workspace): session rename / delete / export / search).#604, opened bydev, labelsarea:dashboard,area:sessions,type:user-story, stateopen/merged: false.changes_requestedreview.pr-changes-requested-graph→ freshaddress-reviewtask enqueued onto the dev pool. Board card should flip torunning(orqueued) for ~12 min and then resolve.task_historyfor(repo='charles/claude-hooks', issue_number=567)contains exactly one row — the original implement task that finishedstatus=success~12 min before this ticket was filed. No second row was inserted. Board card stays inIN REVIEWwith stall age climbing.This is silent — there is no error in the operator's view. The card just sits.
Investigation checklist
pull_request_reviewevent for PR #604 (forge.jacquin.appadmin → System Hooks → Recent Deliveries — there should be a 200 from claude-hooks). If missing, the bug is upstream of the service.apps/server/src/http/webhook.ts→ flow runner →review-requested-graph/pr-changes-requested-graph. Add temporaryconsole.logtaps at each fork if needed; reproduce withcurlagainst127.0.0.1:4500/webhook/forgejousing a saved payload.apps/server/src/domain/workflow/review-loop.ts:17— caps address-review dispatches per PR). Verify the cap counter for PR #604 is below the limit; rule out "we silently hit the cap and fell through".apps/server/src/domain/workflow/event-handlers.ts:338-347): does it fire on this issue, or does the gate (e.g. PR head SHA mismatch, missing labels, session check) drop the request?agents.dbfor<dev>:charles/claude-hooks:567. If a session exists but the resume path is broken, the dispatch may be silently filtered.Acceptance criteria — fix
(Once the cause is known, this section gets sharpened. Initial sketch below.)
apps/server/src/domain/flows/pr-changes-requested-graph.json/.tsapps/server/src/domain/workflow/event-handlers.ts(pivot path)apps/server/src/domain/workflow/review-loop.ts(cap accounting)apps/server/src/http/forge-event-broadcast.ts(envelope routing)pull_request_reviewevent withstate=changes_requestedfor a PR whose previousimplementtask succeeded MUST enqueue a freshaddress-reviewtask, and a follow-up call to the same handler must respect the review-loop cap (so we don't trade one bug for an infinite loop).console.warn/console.logchannels — no new logger.Acceptance criteria — observability
console.logwith enough context ([review-loop] cap hit for ${repo}#${pr}: dispatched=N max=M) so a future operator does not have to git-blame their way to the answer.Out of scope
Force-clear / kill stuck board card from the UI.References
~/.local/state/claude-hooks/tasks.db):apps/server/src/domain/workflow/review-loop.ts— cap mechanism and dispatch-suppression seam.apps/server/src/domain/workflow/event-handlers.ts:338-347— implement→address-review pivot.apps/server/src/domain/flows/pr-changes-requested-graph.jsonand the matching.tsglue.apps/server/src/http/forge-event-broadcast.ts— review event normalisation.https://forge.jacquin.app/-/admin/hooks→ recent deliveries for/webhook/forgejo.state: open,merged: false,merged_at: null,closed_at: null.Root cause analysis
What happened
The webhook did fire (
pull_request_review_rejectedForgejo header ->pull_request_review.submittedtrigger) and thepr-changes-requestedflow DID execute. The investigation found three compounding issues that made it appear as if no dispatch happened:Issue 1 —
task_historyquery used the wrong numberdispatch_addressstores the task withissue_number = src.out.pr.number(the PR number, 604), not the linked issue number (567). A query like:returns zero rows even when address-review was dispatched, because the row has
issue_number=604. This is expected per the graph spec.To find address-review dispatches for PR #604, query
issue_number=604.Issue 2 — Silent FILTER_DROP when author login does not resolve
If
resolve_authorcould not matchdev(the PR'sauthorLogin) to an agent envelope, the entire downstream pipeline dropped with no log line. The operator would see the flow ran but zero node-level side effects, with no indication of why.Fix:
resolveAgentByLoginHandlernow logs:Issue 3 — Silent drop in
guardAuthorDispatchcap-hit and proceed pathsWhen the round cap was hit or the guard passed, there was no log line. The operator could not tell if the guard passed (address-review dispatched) or tripped (force-merge instead).
Fix:
guardAuthorDispatchnow logs on both paths:Fix PR
PR #637 adds:
DispatchOptions.argDefaultsinjection hook for testsdispatchToFlows(pull_request_review_rejected event)-> realpr-changes-requestedgraph ->address-reviewdispatched to author (was completely absent before)pull_request_review_rejectedevent header correctly normalises and routes to apull_request_review.submittedflow