You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(initiative-driver): don't release a sub-issue that is itself an epic (nested-epic-driven-as-story) #976
When an initiative:auto epic is added as a native sub-issue of another initiative:auto epic, the driver — while driving the PARENT — treats the nested epic as a ready story and labels it dev-lead. dev-lead then "implements the epic as a story" and closes it. Incident: epic #934 was nested under armed epic #872; the driver released #934, dev-lead closed it via PR #938, leaving #934's real stories (#935–#937) un-driven.
Fix
In scripts/initiative-driver.sh (the ready-sub-issue selection in drive_epic), skip a sub-issue that is itself an epic — i.e. it carries the initiative label and/or has ≥1 native sub-issue of its own. Such a nested epic is driven independently as its own top-level gated epic (it already appears in the sweep), not released as a story of its parent. Log the skip, e.g. #N — skip: is itself an epic (drive it independently).
Acceptance criteria
When the parent epic is driven, a sub-issue carrying the initiative label (or having its own native sub-issues) is not labeled dev-lead.
The nested epic is still driven on its own (unchanged sweep behavior for top-level gated epics).
Thanks for the detailed write-up — the incident reference to #934/#938 makes the failure mode crystal clear and will be a big help when writing the bats coverage. One clarifying question before the fix lands: the issue describes the skip condition as 'carries the initiative label and/or has ≥1 native sub-issue of its own' — should the implementation treat these as equivalent alternatives (either check is enough to skip), or should one be preferred as the canonical signal (e.g. always use the initiative label, with the sub-issue count only as a fallback)? Knowing which signal is authoritative will keep the guard condition tight and avoid false positives.
Issue:#976 — fix(initiative-driver): don't release a sub-issue that is itself an epic (nested-epic-driven-as-story)
Scope
In drive_epic's ready-sub-issue selection, skip any sub-issue that is itself an epic so the driver never labels a nested epic dev-lead (the #934/#938 incident); the nested epic continues to be driven independently via the sweep.
Detecting "is itself an epic"
The planner labels both epics and stories with the plain initiative label (apply-plan.sh:134 for the epic, :170 for each story), so initiative alone cannot distinguish an epic from a story — keying on it would skip every story and release nothing. The reliable epic signals are:
carries GATE_LABEL (initiative:auto) — an armed nested epic, which is exactly what makes it appear in the sweep and get driven on its own, OR
has ≥1 native sub-issue of its own (structural marker; stories have none).
Implementation Checklist
Add an is_epic helper (gate-label check + native sub-issue count) to scripts/initiative-driver.sh
Call it in the release loop in drive_epic, before labeling; log #N — skip: is itself an epic (drive it independently).
Tests to Write (tests/test_initiative_driver.bats)
Nested epic detected by initiative:auto gate label is skipped (no gh issue edit)
Nested epic detected by having its own native sub-issue is skipped
A normal story (plain initiative, no sub-issues, no gate) is still released — guards against over-skipping
Test Command
bats tests/test_initiative_driver.bats (full suite via lint.yml bats job); lint via bash .dev-lead/scripts/dev-lead-lint.sh
Added sub_issue_numbers helper and a nested-epic skip in drive_epic's release loop
Skip logs #N — skip: is itself an epic (drive it independently).
Added bats coverage for the nested-epic skip (gate-label signal, native-sub-issue signal, and a non-regression "plain story still released" case)
How "is itself an epic" is detected
The skip fires when a candidate sub-issue carries initiative:auto (the gate label) OR has ≥1 native sub-issue of its own.
The plain initiative label is deliberately not used: initiative-planner/apply-plan.sh labels both epics (:134) and their story sub-issues (:170) with initiative, so it cannot distinguish the two — keying on it would skip every story and release nothing. The gate label is the precise signal for the incident class (epic #934 was an initiative:auto epic nested under armed #872) and is exactly what makes the nested epic appear in the sweep to be driven on its own (AC #2). The native-sub-issue count is a structural backstop. The children fetch is a direct assignment (not inside the if condition) so a transient API failure trips errexit instead of being masked into a false "not an epic" — matching the fail-closed behavior of the adjacent blocked_by check.
Test Results
$ bats tests/test_initiative_driver.bats
1..15
ok 1 has_label matches an exact label name
ok 2 has_label with -F does not match regex dot against a different label
ok 3 has_label returns non-zero when label is absent
ok 4 gate: exits 0 with no-op when epic lacks initiative:auto
ok 5 gate: proceeds past gate when epic carries initiative:auto
ok 6 cap: does not label new issues when in-flight equals MAX_IN_FLIGHT
ok 7 blocked_by: does not label an issue that has an open blocker
ok 8 blocked_by: labels a sub-issue when all blockers are closed
ok 9 nested-epic: skips a sub-issue carrying the gate label (drive it independently)
ok 10 nested-epic: skips a sub-issue that has its own native sub-issues
ok 11 nested-epic: a plain story (initiative label, no children, no gate) is still released
ok 12 sweep: drives every open epic carrying initiative:auto when EPIC is empty
ok 13 sweep: a gh failure on one epic surfaces as an error (not masked) and the sweep continues
ok 14 sweep: no-op when no open epic carries initiative:auto
ok 15 cross-repo: a non-self target_repo sweep targets that repo on every API and label call
$ bash .dev-lead/scripts/dev-lead-lint.sh
[lint] shellcheck on 81 file(s)...
[lint] validating 4 agent profile(s)...
[lint] validating .github/CODEOWNERS...
[lint] all checks passed
Full repo bats suite: 911 passing. One unrelated pre-existing failure (tests/test_batch_fallback.bats — "Gemini trust error (55) falls back to Copilot") reproduces with my changes stashed, so it is independent of this change.
Files Changed
scripts/initiative-driver.sh: added sub_issue_numbers helper; in drive_epic's release loop, skip any candidate that is itself an epic (gate label or own native sub-issues) before labeling dev-lead.
tests/test_initiative_driver.bats: added 3 nested-epic tests; updated the blocked_by and cross-repo mocks to model that a plain story has no native sub-issues of its own (their assertions are unchanged — the per-child sub_issues call is new to this code path).
Deviation from the literal "carries the initiative label" wording is intentional and documented above (plain initiative is on every story, so it cannot identify epics).
Activity
github-actions commentedon Jun 28, 2026
Thanks for the detailed write-up — the incident reference to #934/#938 makes the failure mode crystal clear and will be a big help when writing the bats coverage. One clarifying question before the fix lands: the issue describes the skip condition as 'carries the
initiativelabel and/or has ≥1 native sub-issue of its own' — should the implementation treat these as equivalent alternatives (either check is enough to skip), or should one be preferred as the canonical signal (e.g. always use theinitiativelabel, with the sub-issue count only as a fallback)? Knowing which signal is authoritative will keep the guard condition tight and avoid false positives.don-petry commentedon Jun 28, 2026
Dev-Lead Implementation Plan
Issue: #976 — fix(initiative-driver): don't release a sub-issue that is itself an epic (nested-epic-driven-as-story)
Scope
In
drive_epic's ready-sub-issue selection, skip any sub-issue that is itself an epic so the driver never labels a nested epicdev-lead(the #934/#938 incident); the nested epic continues to be driven independently via the sweep.Detecting "is itself an epic"
The planner labels both epics and stories with the plain
initiativelabel (apply-plan.sh:134for the epic,:170for each story), soinitiativealone cannot distinguish an epic from a story — keying on it would skip every story and release nothing. The reliable epic signals are:GATE_LABEL(initiative:auto) — an armed nested epic, which is exactly what makes it appear in the sweep and get driven on its own, ORImplementation Checklist
is_epichelper (gate-label check + native sub-issue count) toscripts/initiative-driver.shdrive_epic, before labeling; log#N — skip: is itself an epic (drive it independently).Tests to Write (
tests/test_initiative_driver.bats)initiative:autogate label is skipped (nogh issue edit)initiative, no sub-issues, no gate) is still released — guards against over-skippingTest Command
bats tests/test_initiative_driver.bats(full suite vialint.ymlbats job); lint viabash .dev-lead/scripts/dev-lead-lint.shdon-petry commentedon Jun 28, 2026
Dev-Lead: Implementation Complete
Plan Execution
sub_issue_numbershelper and a nested-epic skip indrive_epic's release loop#N — skip: is itself an epic (drive it independently).How "is itself an epic" is detected
The skip fires when a candidate sub-issue carries
initiative:auto(the gate label) OR has ≥1 native sub-issue of its own.The plain
initiativelabel is deliberately not used:initiative-planner/apply-plan.shlabels both epics (:134) and their story sub-issues (:170) withinitiative, so it cannot distinguish the two — keying on it would skip every story and release nothing. The gate label is the precise signal for the incident class (epic #934 was aninitiative:autoepic nested under armed #872) and is exactly what makes the nested epic appear in the sweep to be driven on its own (AC #2). The native-sub-issue count is a structural backstop. The children fetch is a direct assignment (not inside theifcondition) so a transient API failure tripserrexitinstead of being masked into a false "not an epic" — matching the fail-closed behavior of the adjacentblocked_bycheck.Test Results
Full repo bats suite: 911 passing. One unrelated pre-existing failure (
tests/test_batch_fallback.bats— "Gemini trust error (55) falls back to Copilot") reproduces with my changes stashed, so it is independent of this change.Files Changed
scripts/initiative-driver.sh: addedsub_issue_numbershelper; indrive_epic's release loop, skip any candidate that is itself an epic (gate label or own native sub-issues) before labelingdev-lead.tests/test_initiative_driver.bats: added 3 nested-epic tests; updated theblocked_byandcross-repomocks to model that a plain story has no native sub-issues of its own (their assertions are unchanged — the per-childsub_issuescall is new to this code path).Notes
dev-lead) and AC Add @claude delegation, auto-merge, and rebase handling #3 (bats coverage) are covered directly. AC Go-live improvements for PR review agent #2 (nested epic still driven independently) is unchanged — top-level gated-epic sweep behavior is untouched; the skip only prevents double-driving the nested epic as a story of its parent.initiativelabel" wording is intentional and documented above (plaininitiativeis on every story, so it cannot identify epics).feat: implement issue #976 — fix(initiative-driver): don't release a …