Follow-up to #443 / PR #454
PR #454 fixed two gaps that left a team-dispatch (multi-implementer) work unit wedged in a slot-occupying phase forever when a dispatch-lifecycle save failed on the publish path:
- The agent-exit handler's fallback-publish branch didn't re-arm the retry on a save failure.
#driveDispatchLifecycle's retry loop for an incomplete published team-dispatch row was gated on !babysitter.enabled, so babysitter-enabled team dispatch had the identical wedge. Fixed by removing that guard, and by only advancing the row to running from #ensureBabysitter once every implementer has a completion PR.
Remaining gap
Verified via targeted instrumentation while testing the babysitter-enabled fix: #ensureBabysitter calls #spawnAgent to place the babysitter agent itself, and #spawnAgent unconditionally saves dispatch-lifecycle phase dispatching as part of normal spawn plumbing — before #ensureBabysitter's new completeness guard ever runs. So even with #454's fix, spawning ONE implementer's babysitter can still durably move a still-incomplete team-dispatch row off published (to dispatching), which #driveDispatchLifecycle handles via #resumeDurableDispatch — a code path built for resuming a stalled/interrupted dispatch, not for retrying a sibling implementer's publish. A sibling implementer that hasn't published yet can still end up without a working retry path in this specific case.
Reproduction
Team dispatch (2 implementers), babysitter enabled. One implementer (pear) fails to publish; the other (hoopsheet) succeeds and its own exit handler calls #ensureBabysitter for its receipt. pear keeps failing until hoopsheet has already published (pins the ordering). Durable phase moves publishing → (hoopsheet's babysitter spawn) → dispatching, and pear's retries never land a receipt. A test exercising exactly this (retries a sibling implementer's publish after another has already reached published, with babysitting enabled) was written and removed from #454's branch before merge because it could not be made to pass without this deeper fix.
Suggested fix
Defer the babysitter spawn itself (not just the running phase-advance already fixed in #454) until #allImplementersHaveCompletionPr(record) is true, for a multi-implementer dispatch. #ensureBabysitter has ~10 call sites; the safest form is likely a single guard near the top of #ensureBabysitter itself (a no-op for single-repo dispatches, where #allImplementersHaveCompletionPr trivially returns true) rather than gating each caller individually. Needs care and its own test coverage given how widely #spawnAgent/#ensureBabysitter are used.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Er8WLsn6HU3C3HHUiX6Uh8
Follow-up to #443 / PR #454
PR #454 fixed two gaps that left a team-dispatch (multi-implementer) work unit wedged in a slot-occupying phase forever when a dispatch-lifecycle save failed on the publish path:
#driveDispatchLifecycle's retry loop for an incompletepublishedteam-dispatch row was gated on!babysitter.enabled, so babysitter-enabled team dispatch had the identical wedge. Fixed by removing that guard, and by only advancing the row torunningfrom#ensureBabysitteronce every implementer has a completion PR.Remaining gap
Verified via targeted instrumentation while testing the babysitter-enabled fix:
#ensureBabysittercalls#spawnAgentto place the babysitter agent itself, and#spawnAgentunconditionally saves dispatch-lifecycle phasedispatchingas part of normal spawn plumbing — before#ensureBabysitter's new completeness guard ever runs. So even with #454's fix, spawning ONE implementer's babysitter can still durably move a still-incomplete team-dispatch row offpublished(todispatching), which#driveDispatchLifecyclehandles via#resumeDurableDispatch— a code path built for resuming a stalled/interrupted dispatch, not for retrying a sibling implementer's publish. A sibling implementer that hasn't published yet can still end up without a working retry path in this specific case.Reproduction
Team dispatch (2 implementers), babysitter enabled. One implementer (
pear) fails to publish; the other (hoopsheet) succeeds and its own exit handler calls#ensureBabysitterfor its receipt.pearkeeps failing untilhoopsheethas already published (pins the ordering). Durable phase movespublishing→ (hoopsheet's babysitter spawn) →dispatching, andpear's retries never land a receipt. A test exercising exactly this (retries a sibling implementer's publish after another has already reached published, with babysitting enabled) was written and removed from #454's branch before merge because it could not be made to pass without this deeper fix.Suggested fix
Defer the babysitter spawn itself (not just the
runningphase-advance already fixed in #454) until#allImplementersHaveCompletionPr(record)is true, for a multi-implementer dispatch.#ensureBabysitterhas ~10 call sites; the safest form is likely a single guard near the top of#ensureBabysitteritself (a no-op for single-repo dispatches, where#allImplementersHaveCompletionPrtrivially returnstrue) rather than gating each caller individually. Needs care and its own test coverage given how widely#spawnAgent/#ensureBabysitterare used.🤖 Generated with Claude Code
https://claude.ai/code/session_01Er8WLsn6HU3C3HHUiX6Uh8