feat(deps): auto-assign unassigned-ready dependents + /unassign escape hatch #201
No reviewers
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!201
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "boss/200"
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?
Summary
Policy pivot on top of #196: when a blocker closes and the dependent issue is unassigned but ready, auto-assign to the suggested agent type and post a one-line audit comment instead of asking the operator to click. At single-operator scale, a manual tap per unblock defeats the point of automation; the heuristic is good enough to try directly and
/unassignmakes the occasional misroute cheap to fix.propagateDependencyClosureunassigned branch →updateIssueAssignees+ auditcreateIssueComment. Forgejo's follow-onissues.assignedwebhook kicks off the real dispatch — we don't callonAssignedReadyfrom the propagator in this path./unassigntrust-gated slash command clears assignees viaPATCH /issues/{N}and posts a short ack. No SQLite persistence — the next close-cycle re-evaluates from state./holdstill exists for the "suppress all automation" semantics.READY_COMMENT_MARKER/renderReadyComment/hasReadyComment/ClearedBlocker: the assignee-state check is the natural dedup for duplicate webhook deliveries./issues/readyendpoint semantics unchanged; it now surfaces the usually-empty "operator-unassigned or pre-boot-ready" slice.Closes #200
Test plan
bun x turbo run typecheck— all three workspace packages cleanbun x biome check .— no lint errorsbun x turbo run test— 849 pass / 0 fail across 41 filesdeps.test.ts::propagateDependencyClosure— unassigned branch now asserts PATCH{ assignees: ["dev"] }+ audit comment (type:chore → dev heuristic),onAssignedReadydoes not firedeps.test.ts::propagateDependencyClosure— new "PATCH failure → no audit comment" casedeps.test.ts::propagateDependencyClosure— new "duplicate delivery → routes through assigned branch, no double auto-assign" casedeps.test.ts::parseUnassignCommand+applyUnassignCommand— new parser + handler coverage, including PATCH-failure pathwebhook-handlers.test.ts::handleIssueComment—/unassignfrom trusted user fires the PATCH + ack; untrusted user is a no-opReview
CI: ✅ green (run #1820, sha
878e35d, 3m53s). Safe to approve.All acceptance criteria from #200 met. Reviewed:
deps.ts,deps.test.ts,webhook-handlers.ts,webhook-handlers.test.ts,forgejo-api.ts.Core logic —
propagateDependencyClosureunassigned branchThe pivot is clean and correct:
updateIssueAssigneesfires first; on failure (!assigned) the function logs +continues — no audit comment is posted for a state change that didn't happen. Good defensive ordering.createIssueCommentfires after the PATCH succeeds; comment failure is warn-logged but doesn't revert the assignment — correct, the authoritative state already changed.onAssignedReadyis not called from this branch — the subsequentissues.assignedwebhook owns dispatch. This is the right design: avoids a double-dispatch and keeps the propagator's surface area narrow.Duplicate-delivery dedup is handled naturally: a second
issues.closedfor the same blocker sees the dependent is now assigned (first pass already ran), routes to theassignees.length > 0branch, andhandleIssueAssigned's existing 30s type-level dedup drops the redundant dispatch./unassignslash command (webhook-handlers.ts)Pattern is correctly mirrored from
/hold:isTrustedUser) before any mutation — ✅probeToken()for the PATCH — consistent with howhandleIssueClosed's propagation gets its token — ✅nullregardless ofapplyUnassignCommand's boolean return — correct; the boolean matters for the propagator's comment-sequencing, not for the webhook responseapplyUnassignCommand(deps.ts)Return semantics are correct: returns
truewhen the PATCH succeeded (assignees cleared), even if the ack comment subsequently fails — because the authoritative state is the Forgejo issue, not the comment. Callers can use the boolean to decide whether to trust that the clear landed.Test coverage
deps.test.tsadditions are thorough:onAssignedReadydoes NOT fire)parseUnassignCommand: first-token-prefix, case-insensitive, mid-line negativeapplyUnassignCommand: success path, PATCH-failure path (ack skipped)webhook-handlers.test.ts: trusted user gets PATCH + ack; untrusted user is a no-op (no PATCH). Correct trust-gate coverage.Minor observations (no action needed)
UNASSIGN_ACKmessage "Re-add a blocker + close it to re-trigger auto-assign" is technically accurate but slightly awkward — it means re-open + re-close an existing one, or add a new one. Acceptable for operator tooling at this scale.AUTO_ASSIGN_COMMENT_MARKERexported as a stable string for future dashboard/scraper use — good forward-thinking.forgejo-api.tschange is a pure docstring update tolistIssueCommentsadding/unassignto the consumer list — ✅ no logic change.LGTM. Clean policy pivot with correct failure sequencing and solid test coverage.