feat(deps): auto-detect blocker closures + ready-to-assign comment #196
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
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/claude-hooks#196
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?
Goal
When a PR merges and closes its source issue, detect every issue that was blocked on it, and — once all their blockers are closed — auto-assign each dependent to the heuristic's pick and stamp a one-line audit comment so the operator can see why and override with
/unassignif wrong.Today the operator (or I) manually track this chain: "OK #175 merged → which of M19-3/4/5/6 unblocks → assign one or more". A handful of dispatches per day, each with a cognitive tax and an opportunity to miss a downstream unblock. Automating detection + routing in one shot keeps the operator-in-the-loop model (override is cheap:
/unassignclears the assignment) but removes the per-unblock tap.Policy pivot note: the original spec called for a "Ready to assign" comment and no auto-assign. After more thought this still required a manual tap per unblock, which defeats the point of automation at a single-operator scale. The heuristic is good enough to try direct auto-assign with a cheap override. The review of PR #198 captured the reasoning and flipped the policy; all the heuristic logic + dependency machinery is reused verbatim — only the final action changes.
Acceptance criteria
Dependency model (source of truth)
POST/GET /repos/{repo}/issues/{index}/dependencies). The claude-hooks repo already hasenable_issue_dependencies: true.Blocks on #N/Dependencies:\s*.*#N/- \*\*Blocks on #M19-\d+\*\*patterns — covers every issue already filed without native deps set (the breakdown skill writes these in prose).Webhook trigger
handleIssueClosed(apps/server/src/webhook-handlers.ts) with a dependency-propagation phase. Fires after the existing cleanup:Dispatch action
issues.assigneddispatch logic (reusehandleIssueAssigned). No comment — the task just starts. The operator was already in the loop when they pre-assigned.assigneesto[<heuristic pick>]via Forgejo's edit-issue route, then stamp a one-line audit comment. Forgejo firesissues.assignedon the PATCH which our own webhook re-enters and dispatches viahandleIssueAssigned. No manual tap required.issues.closed).Audit comment
One-line template (rendered markdown on the dependent issue):
Structural dedup via the
🤖 Auto-assigned toprefix — on a rare re-close loop where the issue is still unassigned and an audit comment is already present, the propagator skips rather than re-assigning over the operator's unassignment.Suggested-assignee heuristic
area:design→ designer,area:design-review→ design-reviewer,area:security→ reviewer-security.area:infra+ body mentions "systemd / docker / reconcile" → bosstype:chore→ devarea:dashboard+ body length > 2 KB → boss (heavy)area:dashboard+ body length ≤ 2 KB → devarea:webhook/area:agents→ boss (architecture-touching)Operator overrides
/hold(or/no-ready) on the issue inserts a row in thehold_issuesSQLite table. The propagator skips that issue on every future blocker closure — no dispatch, no auto-assign — until cleared. Use when parking an issue deliberately./readyclears the hold row. Next blocker closure re-triggers auto-assign./unassignis narrower than/hold: it just clears the issue's assignee list viaPATCH /issues/{N}and posts a one-line ack. Intended for "this particular auto-assign route was wrong". Does NOT toggle the hold flag. Future blocker-close cycles still evaluate the heuristic./breakdown).Integration with breakdown skill
skills/breakdown.md: when creating an issue with aBlocks on #Nbody section, also callPOST /repos/.../dependenciesto set the native dep. Phases out the body-parser fallback over time.just deps-backfill <repo>parses every open issue's body, findsBlocks on #Npatterns, and sets them natively if missing. Idempotent.Observability
GET /issues/readyendpoint returns open issues whose blockers are all closed AND that haven't been silenced via/hold. Feeds the M18-7 assignment board's "Ready" column so it can animate the brief "Ready → assigned" transition as the propagator PATCHes assignees.[deps] #M unblocked by #N — auto-assigned to <agent> (<reasoning>).Tests
deps.test.ts: graph traversal — native deps + body-parser fallback + mixed.deps.test.ts: all-blockers-closed detection — single dep, multi-dep, partial-closed.deps.test.ts: suggested-assignee heuristic over a matrix of label / milestone / body combos.deps.test.ts:propagateDependencyClosureunassigned branch PATCHesassignees:[<type>]and posts the audit comment.webhook-handlers.test.ts:issues.closedfires dispatch when assignee is set, auto-assigns when unassigned, no-ops when blockers remain.webhook-handlers.test.ts:/unassignclears assignees + posts ack (trust-gated).deps.test.ts:/holdcomment suppresses;/readyre-enables.Docs
/hold//ready//unassign./issues/readyendpoint remains for the M18-7 board.skills/breakdown.mdupdated to prefer native deps.Out of scope
Override channel for a mis-routed auto-assign:
/unassign(narrow) or/hold(wholesale suppression).Dependencies
GET /issues/readyendpoint this issue ships.References
POST/GET /repos/{repo}/issues/{index}/dependencies(enabled on claude-hooks viaenable_issue_dependencies: true).apps/server/src/webhook-handlers.ts::handleIssueLabeled.apps/server/src/webhook-handlers.ts::handleIssueClosed— this story extends it.skills/breakdown.md— primary writer of dep-carrying issue bodies.