Skip to content

fix(initiative-driver): don't release a sub-issue that is itself an epic (nested-epic-driven-as-story) #976

Closed

Description

@don-petry

Problem (observed)

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

  1. 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.
  2. The nested epic is still driven on its own (unchanged sweep behavior for top-level gated epics).
  3. bats coverage for the nested-epic skip.

Reference incident: #934 / #938. Refs #882.

Activity

  1. github-actions commented on Jun 28, 2026

    @github-actions
    Contributor

    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.

  2. don-petry commented on Jun 28, 2026

    @don-petry
    CollaboratorAuthor

    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 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

  3. don-petry commented on Jun 28, 2026

    @don-petry
    CollaboratorAuthor

    Dev-Lead: Implementation Complete

    Plan Execution

    • 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).

    Notes

  4. added a commit that references this issue on Jun 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugBug reportsdev-leadFor dev-lead agent pickup

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions