Skip to content

fix(ship): resume pipeline ownership after restarts - #2133

Merged
justinhelmer merged 1 commit into
mainfrom
plan/fix-issue-2116-after-a-r-cf5389/u1
Sep 21, 2026
Merged

justinhelmer merged 1 commit into
mainfrom
plan/fix-issue-2116-after-a-r-cf5389/u1

Conversation

@coreplane-switchboard

@coreplane-switchboard coreplane-switchboard Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

Restarts now retain a ship parent for every child that remains live, including a child whose finish is fenced by a newer generation. This prevents ownerless work and false re-issue guidance while recovery resumes the pipeline.

Why: Issue #2116 exposed child work resuming after a roll while its parent closed; F4 on PR #2133 found the same risk when child finish is refused. Record 0046 makes recovery a fresh admission, so ownership must remain durable across generations.

Where to look

  1. Child liveness selection Classifies foreign, resumable, and failed-but-live child rows before deciding whether their hosted parent survives. ⚠ A false negative can orphan live work and emit re-issue guidance.
  2. Parent rehost decision Combines deadline, Workflow status, and classified children so only a genuinely live pipeline retains its parent. ⚠ A false positive can retain a host key after all work ended.
  3. Fenced finish liveness Treats a fenced close as proof that another generation still owns the child and preserves it in parent-liveness facts. ⚠ This is the F4 race boundary between two generations.
  4. Ledger-derived restart card Names the current unit, pull request, round, and live child from the reclaimed admission instead of fallback prose.
  5. Fresh segment configuration Keeps the preset while bypassing sticky model and effort so resumed work uses configuration in force at recovery. ⚠ Incorrect precedence can run resumed work on stale tuning.
  6. Fenced-finish regression Proves the refused finish leaves no closure, rehosts the parent, and reports the child under the newer generation.
  7. Recovery contract Binds one reclaim admission to parent ownership, child liveness, card reconstruction, and no false re-issue notice.

Feedback wanted: Please scrutinize the fenced-finish liveness inference, terminal-child exclusion, and whether the one-admission card reconstruction selects the correct child.

Risk: 649 changed lines across 14 files. Splitting resumed tuning from ownership recovery was considered, but both shape one restart admission. Wrong liveness can orphan work or retain a host key; rollback is the single commit.

Verified: Four focused Vitest files passed 569 tests; fast gates passed. Pushed CI has 27 completed checks green/skipped with CodeQL pending; a live restart remains human-gated.

Decisions (4)
  • Classify children before parents. A static admission snapshot was rejected because finishing and non-resumable children can close in the same pass. Dependency order gives the parent each child's actual outcome, then restores admission order for callers.
  • Only a fence proves refused work is live. Treating every refused finish as live was rejected because unknown-run means no durable row remains. A fenced result atomically proves another generation owns the row, so only that refusal joins failed-but-live liveness facts.
  • Repaint from one admission. A second store read could race launch and compose stale guidance. Parent events and filtered child rows already form one reclaim snapshot, while repaint failure stays isolated from ownership.
  • Refresh tuning but retain the preset. Pinning the old model and effort was rejected because record 0046 treats resume as a fresh segment. The carried preset preserves the requested agent while current configuration supplies model and effort.
Validation (9 criteria)
Criterion Proof
A fenced child finish cannot close its past-deadline parent Before the production edit, npx vitest run src/core/boot.test.ts failed the new regression because the parent closed interrupted; after the fix it passed 26/26.
Recovery and resumed-dispatch behavior remains green after rebase npx vitest run src/core/boot.test.ts src/core/dispatch/resolve.test.ts src/core/dispatcher.test.ts src/core/resumeLaunch.test.ts passed 4 files and 569 tests.
Changed TypeScript compiles NODE_OPTIONS=--max-old-space-size=6144 npx tsc --noEmit -p tsconfig.json passed with exit 0.
Changed TypeScript matches formatting npx prettier --check on the 10 changed TypeScript files passed.
No new public-tree imprint npm run hygiene:check passed: 9 files, 37 listed hits, 138 allowed lines.
Every spec proof resolves npm run specs:check passed: 50 specs and 5565 proof references checked.
Branch is current with its base Fetched origin/main and rebased the single commit onto f95ad1a; the routing spec conflict retained main's repository facts and this PR's fresh-segment rule.
PR title meets the release gate npm run check:pr-title -- "fix(ship): resume pipeline ownership after restarts" passed.
Completed CI checks are green GitHub check-runs for 4afc77b reported 27 completed checks, all success/skipped, and no failures; CodeQL JavaScript analysis remained in progress.
For agents

Pushed head 4afc77b is one coherent commit rebased onto origin/main at f95ad1a. F4 is covered by the fenced-finish regression in boot.test.ts; it failed before the fix because the past-deadline parent closed and passed afterward. The rebase conflict in routing-and-config.md preserved main's newly merged completed-target facts and this PR's fresh-segment configuration sentence. No generated files changed. CI owns full verification.
Requested by @justinhelmer in slack:C0BRRHKFLCB

🤖 Generated with Claude Code

@coreplane-switchboard coreplane-switchboard Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested: A stale child snapshot can rehost a dead pipeline parent indefinitely, and the updated behavior contradicts an existing run-history criterion.

Warning

Changes requested · head 7dca33b · 2 findings: 1 major, 1 minor

Severity Finding Where
major F1 Admission snapshot can immortalize a parent after its child closes src/core/boot.ts:242
minor F2 Spec contradiction — docs/reference/specs/run-history.md item 36: past-deadline parents now sometimes rehost instead of always closing docs/reference/specs/run-history.md:111
Full review

F1 — childrenOf treats every child in the pre-classification ledger snapshot as live. If a past-deadline parent and a finishing or non-resumable child are reclaimed together, the parent rehosts before that child closes. The launcher then adopts and heartbeats the parent indefinitely even when the Workflow is terminal, suppressing the required interruption notice. Determine parent liveness from children that will actually resume, or re-evaluate the parent after child classification.

F2 — Item 36 still states that every hosted parent past its deadline closes interrupted, while the new criterion and implementation rehost one when its Workflow or child remains live. Update the earlier criterion so the behavioral contract has one rule.

@coreplane-switchboard
coreplane-switchboard Bot force-pushed the plan/fix-issue-2116-after-a-r-cf5389/u1 branch from 7dca33b to 0a5727d Compare September 21, 2026 16:42

@coreplane-switchboard coreplane-switchboard Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested: The prior findings are fixed, but a transient child-classification failure can still close its live parent and emit re-issue guidance.

Warning

Changes requested · head 0a5727d · 1 finding: 1 major

Severity Finding Where
major F3 A failed child classification can orphan the child and close its parent src/core/boot.ts:251
Full review

F3 — If classifying a reclaimed child throws—for example, readEvents transiently fails—the catch leaves its row live in outcome.failed, but childrenOf excludes it because it never entered resumingIds. A past-deadline parent can then close and post “re-issue” while that child remains live and owned by this generation; periodic sweeps will not retry a self-owned row. Treat failed-but-still-live children as parent liveness, or defer the parent when any child classification fails.

@coreplane-switchboard
coreplane-switchboard Bot force-pushed the plan/fix-issue-2116-after-a-r-cf5389/u1 branch 2 times, most recently from b9f9712 to d96058c Compare September 21, 2026 16:56

@coreplane-switchboard coreplane-switchboard Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested: F3's reported throw path is fixed, but a non-throwing child finish refusal can still orphan the live child from its parent.

Warning

Changes requested · head d96058c · 1 finding: 1 major

Severity Finding Where
major F4 A refused child finish is still omitted from parent liveness src/core/boot.ts:372
Full review

F4 — When closeReclaimed returns a non-throwing refusal such as fenced, the child is added to outcome.failed but not failedIds. If another generation now owns that still-live child, childrenOf excludes it and the past-deadline parent can close with re-issue guidance. Add refused-but-live child classifications to the parent-liveness facts and cover the refusal path.

Co-Authored-By: coreplane-switchboard[bot] <318072483+coreplane-switchboard[bot]@users.noreply.github.com>
@coreplane-switchboard
coreplane-switchboard Bot force-pushed the plan/fix-issue-2116-after-a-r-cf5389/u1 branch from d96058c to 4afc77b Compare September 21, 2026 17:06

@coreplane-switchboard coreplane-switchboard Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM: F4 is fixed: fenced child-finish refusals now remain parent-liveness facts, with regression coverage for the foreign-live-child path.

Note

Approved · head 4afc77b · no findings

Full review

F4 is resolved: fenced child-finish refusals now preserve the child as a parent-liveness fact, and the regression covers the foreign-owner path. No findings remain.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auto-approved: coreplane-switchboard[bot] reviewed this PR and posted an LGTM verdict (see its review). This repository opted in through its REVIEW_BOT_LOGIN and REVIEW_BOT_ID variables.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant