feat(flows): NF-6 Phase 4F — issue-closed-deps baked-in flow #385

Merged
charles merged 2 commits from feat/flows-issue-closed-deps into main 2026-04-26 14:45:35 +00:00
Collaborator

Summary

Ports webhook-handlers.ts::handleIssueClosed (lines 601-630) into a baked-in node-flow graph. Ships issue-closed-deps as the seventh seeded flow on the issue.closed trigger.

src → forge.handle_issue_closed_cleanup

One monolithic node forge.handle_issue_closed_cleanup wraps the legacy fire-and-forget which launches:

  1. cleanupIssue(repo, issueNumber, agents) — releases worktrees + drops sessions across every agent.
  2. propagateDependencyClosure({ repo, closedIssueNumber, … }) — re-dispatches blocked issues whose blocker just closed (#196 / #200), auto-assigning unassigned issues with an audit comment.

Monolithic because the legacy code is also monolithic — both side effects fire unconditionally on every issue.closed with no flow-author-meaningful boundary between them. Errors INSIDE the wrapped helpers are caught + logged by them; the wrapper does NOT try/catch around the synchronous launch step so an upstream wiring bug surfaces as a flow error rather than soft-failing.

Cutover

node_flows.suppress_legacy: ["issue.closed"] skips the legacy switch arm at webhook.ts:256-258 once the flow soaks clean.

Test plan

  • 10 e2e tests: happy path, throw → error surface, not-wired error
  • Full flows suite: 303 pass / 0 fail
  • Server typecheck clean
  • AGENT_NODE_COUNT bumped from 11 to 12
  • Soak in mode: "live" against real issue.closed, confirm parity via GET /flows/divergence/summary
  • Flip suppress_legacy: ["issue.closed"] after clean soak

Stacking note

This branch is off main (currently at PR #383's merge commit). PR #384 (Phase 4E) is also open against main. Both touch agent-nodes.ts and flow-dispatch.ts — expect merge-order conflicts on AGENT_NODE_COUNT + the registration list. The conflicts are mechanical (additive lines, no shared logic).

🤖 Generated with Claude Code

## Summary Ports `webhook-handlers.ts::handleIssueClosed` (lines 601-630) into a baked-in node-flow graph. Ships `issue-closed-deps` as the seventh seeded flow on the `issue.closed` trigger. ``` src → forge.handle_issue_closed_cleanup ``` One monolithic node `forge.handle_issue_closed_cleanup` wraps the legacy fire-and-forget which launches: 1. `cleanupIssue(repo, issueNumber, agents)` — releases worktrees + drops sessions across every agent. 2. `propagateDependencyClosure({ repo, closedIssueNumber, … })` — re-dispatches blocked issues whose blocker just closed (#196 / #200), auto-assigning unassigned issues with an audit comment. Monolithic because the legacy code is also monolithic — both side effects fire unconditionally on every `issue.closed` with no flow-author-meaningful boundary between them. Errors INSIDE the wrapped helpers are caught + logged by them; the wrapper does NOT try/catch around the synchronous launch step so an upstream wiring bug surfaces as a flow `error` rather than soft-failing. ## Cutover `node_flows.suppress_legacy: ["issue.closed"]` skips the legacy switch arm at `webhook.ts:256-258` once the flow soaks clean. ## Test plan - [x] 10 e2e tests: happy path, throw → error surface, not-wired error - [x] Full flows suite: 303 pass / 0 fail - [x] Server typecheck clean - [x] AGENT_NODE_COUNT bumped from 11 to 12 - [ ] Soak in `mode: "live"` against real `issue.closed`, confirm parity via `GET /flows/divergence/summary` - [ ] Flip `suppress_legacy: ["issue.closed"]` after clean soak ## Stacking note This branch is off `main` (currently at PR #383's merge commit). PR #384 (Phase 4E) is also open against main. Both touch `agent-nodes.ts` and `flow-dispatch.ts` — expect merge-order conflicts on `AGENT_NODE_COUNT` + the registration list. The conflicts are mechanical (additive lines, no shared logic). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(flows): NF-6 Phase 4F — issue-closed-deps baked-in flow
Some checks failed
qa / qa (pull_request) Has been cancelled
qa / dockerfile (pull_request) Has been cancelled
504ec7c802
Ports `webhook-handlers.ts::handleIssueClosed` (lines 601-630) into a
baked-in node-flow graph. Ships `issue-closed-deps` as the sixth
seeded flow on the `issue.closed` trigger:

  src → forge.handle_issue_closed_cleanup

One monolithic node `forge.handle_issue_closed_cleanup` wraps the
legacy fire-and-forget which launches two side effects:
  1. `cleanupIssue(repo, issueNumber, agents)` — releases worktrees +
     drops sessions across every agent.
  2. `propagateDependencyClosure({ repo, closedIssueNumber, … })` —
     re-dispatches blocked issues whose blocker just closed
     (#196 / #200), auto-assigning unassigned issues with an audit
     comment.

Monolithic because the legacy code is also monolithic — both side
effects fire unconditionally on every `issue.closed` with no
flow-author-meaningful boundary between them. Errors INSIDE the
wrapped helpers are caught + logged by them; the wrapper does NOT
try/catch around the synchronous launch step so an upstream wiring
bug surfaces as a flow `error` rather than soft-failing.

Cutover gate: `node_flows.suppress_legacy: ["issue.closed"]` skips the
legacy switch arm at `webhook.ts:256-258` once the flow soaks clean
on `GET /flows/divergence/summary`.

Tests: 10 e2e tests covering happy path, throw → error surface,
not-wired error.
AGENT_NODE_COUNT bumped from 11 to 12.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
docs(flows): issue-closed-deps — round-1 review hardening
All checks were successful
qa / qa (pull_request) Successful in 6m30s
qa / dockerfile (pull_request) Successful in 13s
3c4c675ec3
Two doc-only items from subagent review of #385:

1. **`HandleIssueClosedFn` "MUST stay sync void" warning.** The flow
   executor resolves handler returns through `Promise.resolve().then(...)`,
   so an `async () => { await handleIssueClosed(...) }` wrapper would
   silently flip the fire-and-forget semantics into await-completion,
   blocking the flow run on `cleanupIssue` + `propagateDependencyClosure`
   finishing (worst-case minutes when a dep chain is deep). Type doc
   now declares the contract.

2. **`onAssignedReady` re-enters legacy `handleIssueAssigned`.** When
   `suppress_legacy: ["issue.closed"]` flips but `"issue.assigned"` is
   not yet on the list, dependency-triggered re-dispatches still go
   through legacy code — the `default` flow doesn't observe them. TODO
   comment added to swap the callback to `dispatchToFlows` re-entry
   once the assigned flow soaks too.

No code changes; tests + typecheck still clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
charles deleted branch feat/flows-issue-closed-deps 2026-04-26 14:45:35 +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!385
No description provided.