Skip to content

fix: session.idle with stale background-task payload no longer blocks IsProcessing#529

Closed
PureWeen wants to merge 1 commit intomainfrom
shneuvil/fix-idle-defer-stale-background-tasks
Closed

fix: session.idle with stale background-task payload no longer blocks IsProcessing#529
PureWeen wants to merge 1 commit intomainfrom
shneuvil/fix-idle-defer-stale-background-tasks

Conversation

@PureWeen
Copy link
Copy Markdown
Owner

@PureWeen PureWeen commented Apr 6, 2026

Summary

  • What broke: Race in IDLE-DEFER — backgroundTasksChanged shells=0 fires (PolyPilot confirms all shells done), then session.idle arrives with shells=2 (stale CLI snapshot generated before completions landed). PolyPilot started a fresh 60-min zombie clock from the stale payload, keeping IsProcessing=true indefinitely.
  • What the fix does: Capture preIdleFingerprint / preIdleTicks before calling RefreshDeferredBackgroundTaskTracking. If they were null/0 (meaning backgroundTasksChanged already confirmed empty), the session.idle payload is stale — skip the defer entirely. PolyPilot's real-time backgroundTasksChanged tracking is the ground truth over the CLI's snapshot.
  • Which invariant it restores: INV-O3 / IDLE-DEFER safety — a session stuck with IsProcessing=true and no active background tasks must eventually complete. The zombie timeout was being bypassed by the timer reset.

Observed failure

PROMPT session — last console.log entries:

17:12:16 [BG-TASKS] 'PROMPT' background tasks changed (agents=0, shells=0, ...)
17:12:21 [BG-TASKS] 'PROMPT' background tasks changed (agents=0, shells=0, ...)
17:12:27 [IDLE-DIAG] 'PROMPT' session.idle payload: backgroundTasks={agents=0, shells=2, null=False}
17:12:27 [IDLE-DEFER] 'PROMPT' session.idle received with active background tasks — deferring completion (IsProcessing=True, ...)
17:15:38 [WATCHDOG] 'PROMPT' Case B deferred — events.jsonl modified since turn start (deferral=1/40, freshness=1800s)

PolyPilot's own tracking: shells=0. CLI idle payload: shells=2. Session stuck.

Test plan

  • SessionIdle_StalePayload_NotDeferredWhenBgTasksAlreadyConfirmedEmpty — structural test verifying the staleness guard is present and wired correctly
  • Existing BackgroundTasksIdleTests pass (14/16; 2 pre-existing unrelated failures on SessionBackgroundTasksChangedEvent string search)
  • Verify PROMPT session exits IsProcessing after next PolyPilot relaunch with this fix

… completion

Race condition in IDLE-DEFER: backgroundTasksChanged fires with shells=0 (PolyPilot
confirms all shells done) then a session.idle arrives with shells=2 (stale CLI snapshot
generated before completions landed). PolyPilot was starting a fresh 60-min zombie
clock on the stale payload, keeping IsProcessing=true indefinitely.

Fix: capture pre-idle fingerprint/ticks before calling RefreshDeferredBackgroundTaskTracking.
If they were null/0 (backgroundTasksChanged already confirmed empty), treat the idle
payload as stale and skip the defer. PolyPilot's real-time tracking is ground truth.

Observed in the PROMPT session: agents all completed (agents=0), backgroundTasksChanged
showed shells=0, but session.idle fired with shells=2 — session stuck in IsProcessing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@PureWeen PureWeen closed this Apr 6, 2026
@PureWeen
Copy link
Copy Markdown
Owner Author

PureWeen commented Apr 6, 2026

Changes folded into PR #528.

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