fix(gh-aw): refill dispatch slots from the root, not the parent epic - #1787
Conversation
The implement-worker refilled a freed dispatch slot by dispatching `/squad implement` against the completing task's *immediate parent epic*. In a three-level tree (root -> epics -> leaf tasks) that scopes the refill to one epic: when Epic A drains, the worker finds nothing, exits green, and Epic B's unstarted leaf tasks are never reached. Slots sit idle with no error surfaced -- the same silent-no-op signature as #1772. Walk the parent chain to the root issue and dispatch that instead. squad.md's implement mode already descends the full sub-tree recursively (#1778), so root-scoped dispatch covers every sibling epic without any new traversal code in the worker. Budgets are untouched: dispatch-workflow max stays at 2 and the 3-slot concurrency cap in squad.md's Epic Dispatch still governs selection. The bug was traversal scope, not budget, and raising max would widen blast radius on the empty-probe path closed in #1778. Leaf-only rule hardened, not regressed. Root-scoped refill descends past drained-but-still-open epics, which have no *open* sub-issues and so passed squad.md's previous leaf test -- reviving the #1758 defect 2 shape at the end of an epic's life. Leafness is now "no sub-issues at all" plus not labeled epic/initiative. Testing: test/gh-aw-implement-workflow.test.ts gains a traversal simulator rather than a wording assertion (#1784 showed prompt-text presence cannot prove prompt obedience). It parses the shipped dispatch payload, binds its issue_number placeholder to the traversal it names, and runs that traversal over a two-sibling-epic fixture using squad.md's own leaf rule and slot budget. Verified red against the pre-fix workflows: 3 failures, including "refill must scan from the root, not the drained parent epic: expected 110 to be 100". Closes #1779 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 70370e36-33b0-4786-bd72-4cf15518daa6
🟡 Impact Analysis — PR #1787Risk tier: 🟡 MEDIUM 📊 Summary
🎯 Risk Factors
📦 Modules Affectedroot (2 files)
tests (2 files)
This report is generated automatically for every PR. See #733 for details. |
🛫 PR Readiness Check
PR Scope: 🔧 Infrastructure
|
| Status | Check | Details |
|---|---|---|
| ✅ | Single commit | 1 commit — clean history |
| ✅ | Not in draft | Ready for review |
| ✅ | Branch up to date | Up to date with dev |
| ❌ | Copilot review | No Copilot review yet — it may still be processing |
| ✅ | Changeset present | No source files changed — changeset not required |
| ✅ | Scope clean | No .squad/ or docs/proposals/ files |
| ✅ | No merge conflicts | No merge conflicts |
| ❌ | Copilot threads resolved | 2 unresolved Copilot thread(s) — fix and resolve before merging |
| ✅ | CI passing | All checks passing |
Files Changed (4 files, +275 −10)
| File | +/− |
|---|---|
test/gh-aw-implement-workflow.test.ts |
+237 −1 |
test/gh-aw-quality.test.ts |
+1 −1 |
workflows/squad-implement-worker.md |
+27 −5 |
workflows/squad.md |
+10 −3 |
Total: +275 −10
This check runs automatically on every push. Fix any ❌ items and push again.
See CONTRIBUTING.md and PR Requirements for details.
There was a problem hiding this comment.
Pull request overview
This PR fixes the implement-worker “refill” traversal so that, after a leaf task PR merges, the worker dispatches /squad implement against the root of the issue tree (not the immediate parent epic). This prevents freed concurrency slots from going idle when sibling epics still have unstarted leaf tasks, and adds structural tests to prove the refill scope and leaf-only behavior.
Changes:
- Update the implement worker continuation dispatch payload to target
{root-issue-number}instead of{parent-epic-number}. - Tighten
squad.md’s leaf-task definition to avoid dispatching drained-but-open grouping issues (epics/initiatives). - Add a new structural test suite that parses the shipped dispatch payload and simulates refill traversal over a fixture tree, including a negative control and budget pin.
Show a summary per file
| File | Description |
|---|---|
| workflows/squad.md | Tightens leaf-task criteria for implement-mode dispatch selection. |
| workflows/squad-implement-worker.md | Changes merge-continuation behavior to resolve and dispatch from the root issue. |
| test/gh-aw-quality.test.ts | Updates the continuation dispatch contract to expect {root-issue-number}. |
| test/gh-aw-implement-workflow.test.ts | Adds structural traversal/budget/leaf-only tests for cross-sibling refill behavior. |
Review details
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
- Files reviewed: 4/4 changed files
- Comments generated: 2
- Review effort level: Lite
| 5. Selection and budget stay where they already are. `squad`'s implement mode | ||
| dispatches only **leaf tasks** — open descendants with no open sub-issues — | ||
| and never an epic or the root itself, and it caps concurrent work with its | ||
| own available-slots calculation. Do not pre-select tasks, widen any cap, or | ||
| dispatch a worker directly from here to compensate for a drained epic. |
| 4. Identify the **leaf tasks**: open descendants that have **no sub-issues at | ||
| all** — neither open nor closed — and are not labeled `epic` or `initiative`. | ||
| Intermediate parents (initiatives and epics that only group other issues) | ||
| are never dispatched to a worker — only leaf tasks are implemented. Use "no | ||
| sub-issues at all" rather than "no *open* sub-issues": an epic whose children |
* docs(e2e): add E4 agent-binding verification procedure for #1784 Adds .squad/e2e/E4-agent-binding-verification.md: a runnable, not-yet-executed procedure to verify #1784 (plan activate minting squad:lead/squad:devrel as owner labels instead of cast names) once Procedures' fix merges. Targets ~27 min instead of a full ~54 min long-path run by stopping at `plan implementation`, which is where the Agent-column leak originates (workflows/squad.md:913) rather than where it surfaces (:730). Key contents: - Three-way PASS/PARTIAL/FAIL criterion, stated before the run. squad:devrel is dispositive (DevRel appears nowhere in the fixture roster, verified), while squad:lead is ambiguous (Keaton's Role column reads "Lead / Architect"). A binary criterion would score a real partial fix as FAIL. - Fixture-refresh phase covering the traps that produce false results: two files named squad.md (56,525 vs 6,688 bytes), two independently stale surfaces (compiled lock + committed source), GH_AW_INFO_FRONTMATTER_SOURCE being provenance only, and PowerShell backtick/ConvertFrom-Json pitfalls. - Seed issue shaped for >=2 sibling epics per EECOM, with an explicit guard rail against contorting the fixture to force that shape. - #1779 / PR #1787 refill verification kept as a clearly separated follow-on with its own criterion, marked not-unattended-safe. Refs #1784. Deliberately does not close it: this procedure has not been executed and #1784 must not be marked verified until it runs green post-merge. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * docs(e2e): replace briefed observation with direct artifact read; fix label check Read run 32433493989 (plan implementation) and 32435055598 (plan activate) directly rather than relying on the relayed observation. Briefed counts confirmed exactly, plus four findings — one of which was a defect in this procedure. Confirmed: Agent column is `lead`, `lead`, `devrel` (lead x2, devrel x1); labels minted are squad:lead x3 + squad:devrel x1. New findings: - Squad's own "Validation Pre-check" printed "Agent assignments valid (cast Names) | OK (lead, lead, devrel)" — it passes on exactly the input it should reject. Added an explicit instruction not to trust it. - Zero legitimate cast-name labels have ever been minted (all five roster names ABSENT). The binding has never once worked. - squad:reviewer also leaked, in E1 (#9) — so all three tokens named in the two prohibitions have leaked, and nothing outside that set ever has. Added squad:reviewer to the criterion as a second dispositive token. - The plan program artifact contains NO owner assignment, so epic #17's squad:lead is minted at plan activate — downstream of where E4 stops. Documented as a scope limitation: a green E4 means the Agent column is clean, not that no label leaks. Procedure defect fixed: Phase 3b checked repo-global label existence, but labels persist once created and squad:lead/devrel/reviewer already exist from E1 and E3. That check returns true forever and would have failed a perfect run. Rescoped to issues created after an $E4_START cutoff, with the pre-existing contamination tabulated. Refs #1784. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: brady gaster <bradygaster@github.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
E4 executed 2026-08-21 against fixture bradygaster/aspiregregator-squad-e2e (seed #22). 8 gates, 8 green on first attempt, zero interventions, ~55 min. Verdict PASS on all four conditions: - Cond 0: 15 squad-authored issues created by activate (#23-#37) - Cond 1: Agent column 11/11 verbatim roster names, zero role-derived tokens - Cond 2: 15 timeline labeled events, all 'squad'; zero unlabeled - Cond 3: activation summary reports the missing-label prerequisite gap Same-fixture control: E3 produced 'lead, lead, devrel' on this exact column. Two qualifications recorded so the result is not overstated: - The activate-stage roster read still returns the generic uncast vocabulary (lead/reviewer/devrel/security/docs) while citing a file whose Name column is Keaton/McManus/Fenster/Hockney/Kint. Cond 2's green is structural (no create-label configured), not earned. Likely a separate defect from #1784. - The fixture cannot express a correct owner label at all; closing #1784's user-visible symptom needs an issues:write + create-label change. Also adds a known-gap note that the criterion detects wrong vocabulary, not wrong assignment, and records the first live #1787 observation: all 15 issues parent to root, epics have zero sub-issues, so refill draws from the root. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 34df7b7a-c954-4109-99c7-7c20c72bf2a0
…1813) * docs(e2e): add E4 agent-binding verification procedure for #1784 Adds .squad/e2e/E4-agent-binding-verification.md: a runnable, not-yet-executed procedure to verify #1784 (plan activate minting squad:lead/squad:devrel as owner labels instead of cast names) once Procedures' fix merges. Targets ~27 min instead of a full ~54 min long-path run by stopping at `plan implementation`, which is where the Agent-column leak originates (workflows/squad.md:913) rather than where it surfaces (:730). Key contents: - Three-way PASS/PARTIAL/FAIL criterion, stated before the run. squad:devrel is dispositive (DevRel appears nowhere in the fixture roster, verified), while squad:lead is ambiguous (Keaton's Role column reads "Lead / Architect"). A binary criterion would score a real partial fix as FAIL. - Fixture-refresh phase covering the traps that produce false results: two files named squad.md (56,525 vs 6,688 bytes), two independently stale surfaces (compiled lock + committed source), GH_AW_INFO_FRONTMATTER_SOURCE being provenance only, and PowerShell backtick/ConvertFrom-Json pitfalls. - Seed issue shaped for >=2 sibling epics per EECOM, with an explicit guard rail against contorting the fixture to force that shape. - #1779 / PR #1787 refill verification kept as a clearly separated follow-on with its own criterion, marked not-unattended-safe. Refs #1784. Deliberately does not close it: this procedure has not been executed and #1784 must not be marked verified until it runs green post-merge. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * docs(e2e): replace briefed observation with direct artifact read; fix label check Read run 32433493989 (plan implementation) and 32435055598 (plan activate) directly rather than relying on the relayed observation. Briefed counts confirmed exactly, plus four findings — one of which was a defect in this procedure. Confirmed: Agent column is `lead`, `lead`, `devrel` (lead x2, devrel x1); labels minted are squad:lead x3 + squad:devrel x1. New findings: - Squad's own "Validation Pre-check" printed "Agent assignments valid (cast Names) | OK (lead, lead, devrel)" — it passes on exactly the input it should reject. Added an explicit instruction not to trust it. - Zero legitimate cast-name labels have ever been minted (all five roster names ABSENT). The binding has never once worked. - squad:reviewer also leaked, in E1 (#9) — so all three tokens named in the two prohibitions have leaked, and nothing outside that set ever has. Added squad:reviewer to the criterion as a second dispositive token. - The plan program artifact contains NO owner assignment, so epic #17's squad:lead is minted at plan activate — downstream of where E4 stops. Documented as a scope limitation: a green E4 means the Agent column is clean, not that no label leaks. Procedure defect fixed: Phase 3b checked repo-global label existence, but labels persist once created and squad:lead/devrel/reviewer already exist from E1 and E3. That check returns true forever and would have failed a perfect run. Rescoped to issues created after an $E4_START cutoff, with the pre-existing contamination tabulated. Refs #1784. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * docs(e2e): correct Phase 0d to assert the .md, and record the fixture sync The fixture had drifted: workflows/squad.md was 4,665 bytes behind dev and squad-implement-worker.md 1,731 behind, so E4 would have executed the pre-#1789 prompt. Synced and recompiled (fixture commit 1b11c5f); all five prompt sources now byte-identical to dev by blob SHA. Phase 0d changes: - Assert BOTH directions. #1789 replaced the old prohibition rather than appending, so "never mint a role-derived" must be ABSENT, not just Check 10 present. An addition-only check passes a file carrying both rules at once, which is worse than either alone. - Replace the speculative DevRel/squad:lead probe with the now-known markers (Check 10 / sole source of truth / Non-roster), since #1789 has merged. - Warn explicitly against asserting on squad.lock.yml. The lock does not inline the prompt body; it emits {{#runtime-import .github/workflows/squad.md}} resolved at runtime, so the markers cannot appear there and checking it returns a guaranteed false FAIL. The lock's body_hash is what proves a recompile ingested the new source. Each artifact asserted for what it can actually represent. - Fix the gh api call: add ?ref=main and the -join/-replace needed before FromBase64String. Refs #1784, #1789. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * docs(e2e): record E4 agent-binding verification result E4 executed 2026-08-21 against fixture bradygaster/aspiregregator-squad-e2e (seed #22). 8 gates, 8 green on first attempt, zero interventions, ~55 min. Verdict PASS on all four conditions: - Cond 0: 15 squad-authored issues created by activate (#23-#37) - Cond 1: Agent column 11/11 verbatim roster names, zero role-derived tokens - Cond 2: 15 timeline labeled events, all 'squad'; zero unlabeled - Cond 3: activation summary reports the missing-label prerequisite gap Same-fixture control: E3 produced 'lead, lead, devrel' on this exact column. Two qualifications recorded so the result is not overstated: - The activate-stage roster read still returns the generic uncast vocabulary (lead/reviewer/devrel/security/docs) while citing a file whose Name column is Keaton/McManus/Fenster/Hockney/Kint. Cond 2's green is structural (no create-label configured), not earned. Likely a separate defect from #1784. - The fixture cannot express a correct owner label at all; closing #1784's user-visible symptom needs an issues:write + create-label change. Also adds a known-gap note that the criterion detects wrong vocabulary, not wrong assignment, and records the first live #1787 observation: all 15 issues parent to root, epics have zero sub-issues, so refill draws from the root. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 34df7b7a-c954-4109-99c7-7c20c72bf2a0 * docs(e2e): correct E4 Finding A - create-label claim refuted, defect is stage-local Two corrections to the E4 result, both measured: 1. REFUTED: the claim that with create-label enabled this run would have minted squad:lead again. activate applies what the plan hands it; E3's plan column was 'lead, lead, devrel' (exactly its minted set) while E4's contains only cast names, so there is no role token to apply. create-label is required for the correct behaviour, not what suppressed the defect. Condition 2's green is still structural, but the structure is the roster mismatch rather than the missing permission. 2. The activate roster read is stage-local and provably capable of being correct: plan validate in the same walk, 23 minutes earlier, read the same file/section/column correctly and verified all four Agent values verbatim via Check 10. Not a wrong path, not a hardcoded default, not a shared component - a working reference implementation exists one stage earlier. Also records that E3's validate enumerated no roster at all (false accept by omission, not by wrong roster), so #1789 added the roster echo and Check 10 at validate and left activate untouched. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 34df7b7a-c954-4109-99c7-7c20c72bf2a0 --------- Co-authored-by: brady gaster <bradygaster@github.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 34df7b7a-c954-4109-99c7-7c20c72bf2a0
Closes #1779
Working as EECOM (Core Dev).
What the traversal did before vs after
Before. After a task's PR merged,
squad-implement-worker.mdresolved the child's immediate parent epic and dispatched/squad implementagainst it ("issue_number": "{parent-epic-number}"). In a 2-level tree (Root → Tasks) that is the whole tree, so it worked. In a 3-level tree (Root → [Epic A, Epic B] → leaf Tasks) it scoped the refill to one epic:implementon Epic A.Same silent-no-op signature as #1772 — looks green, work is stalled.
After. The worker walks the parent chain upward from the parent epic (native parent relationship,
Parent: #Nbody line as fallback, cycle-guarded by a visited set) to the root issue, and dispatches that:"issue_number": "{root-issue-number}".squad.md's implement mode already descends the full sub-issue tree recursively (shipped in #1778), so a root-scoped dispatch reaches every sibling epic. No new traversal code was added to the worker — the fix names a wider target for traversal that already exists.Degradation is explicit: if the chain cannot be walked past the parent epic, the parent epic is used as the root rather than skipping the dispatch. Widening the scan must never turn into no scan.
Dispatch budgets: untouched, deliberately
dispatch-workflow: maxstays at 2 andsquad.md'savailable-slots = max(0, 3 - active-implementation-count)still governs selection. The defect was traversal scope, not budget — the slot was free, the worker just never looked where the work was. Raisingmaxagain would widen blast radius on the empty-probe path closed in #1778 while leaving sibling epics stranded. The new test assertsmax === 2with the message "refill scope is the fix; do not paper over it by raising max", so a future budget-bump workaround fails CI.Leaf-only rule: hardened, not regressed
Root-scoped refill surfaced a latent hole in
squad.md's leaf definition. It read "open descendants that themselves have no open sub-issues". A drained-but-still-open epic — every child implemented and closed, the epic itself not yet closed — passes that test and would be dispatched to a worker as if it were implementable. That is the #1758 defect 2 failure shape reappearing at the end of an epic's life. It was mostly unreachable before because the refill never descended past its own epic; root-scoped refill walks straight into it.Found by the new test going red on
expected [110, 121, 122] to not include 110during development, not by inspection.Leafness is now "no sub-issues at all" (neither open nor closed) and not labeled
epic/initiative. The#1758.2behavior confirmed live is preserved and strictly tightened — a dedicated test asserts a drained epic is never dispatched.Proof the test fails pre-fix
Per the workstream bar, and heeding #1784: a test asserting prompt text is present cannot prove the prompt is obeyed. So this is not a wording assertion.
test/gh-aw-implement-workflow.test.tsnow:issue_numberplaceholder to the traversal it names (unknown placeholder = hard failure, not a silent pass).squad.md's own leaf-only descent and slot budget.Fixture — root, two sibling epics, leaves under each:
Verified red by stashing only
workflows/and re-running:Post-fix:
Test Files 3 passed (3) | Tests 126 passed | 13 skipped (139).New tests:
The control test is deliberate: it runs the traversal with the literal pre-fix token and asserts it strands work, so the model itself is proven capable of showing red.
Validation
gh aw compile --strict(squad.md)1 succeededgh aw compile --strict(squad-implement-worker.md)1 succeededstrict-compiles and preserves prompt/config behaviorgate testgh awv0.86.2 present)scripts/check-workflow-input-interpolation.mjsnpm run buildgh-aw-implement-workflow+gh-aw-quality+gh-aw-plan-lifecycle+pr-readiness+docs-linksStaged diffstat:
git diff --cached --diff-filter=D --name-only→ empty. No changeset: nopackages/*/srcfiles touched. Build-mutatedpackage.json/package-lock.json/templates/skills/release-process/SKILL.mdfiles were restored and are not in this commit.Notes for reviewers
{parent-epic-number}were updated in the same commit (test discipline — API change and test change together). The{parent-epic-number}string that remains ingh-aw-quality.test.tsis an intentionally-malformed gate fixture, unrelated.squad.mdis Procedures' file. The leaf-rule tightening there is in scope because this change is what makes the drained-epic case reachable — fixing scope without fixing that would hand a grouping issue to a worker. Happy to split if preferred, but they should land together.app/github-actionsparkpull_requestruns inaction_required, and gh-aw'sdetectionjob can hang, so live end-to-end confirmation of this path still needs a supervised run against a multi-epic fixture.