Background session's state.json keeps a stale inFlight.queued: 1, so the daemon never retires the idle session

Status Open
Reported on v2.1.301
Maintainer reply None cached
Activity 0 comments · opened Oct 16, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

A background session's state.json keeps inFlight.queued: 1 after the session drains its queue, and the daemon never retires that session's process because of it. Nothing is queued; the count is stale.

The in-flight snapshot is published from a component that reads the queue length while rendering, sessionCrons:!1}),L=Cu().getMainThreadQueueLength() in 2.1.301, without subscribing to the queue. It re-renders only when background tasks or todos change. When a background task finishes, its state change and its queued task notification land together, so that render sees 1. The notification is then dequeued, to start a wake-up turn or folded into a running turn, and nothing renders again, so 1 is written into every later state.json update. Another consumer in the same binary subscribes correctly: Rt(Cn.subscribe,()=>Cn.getMainThreadQueueLength()>0).

The daemon reads that field as work in flight. retireIfSettled returns {retired:!1,reason:"inflight"} when (n.inFlight?.queued??R)>0, which blocks idle retirement, the low-memory shed and the pinned-session pass. The stale-version retirement requires queued===0, and respawnIfIdleStale refuses too, so the session also never moves onto a newer binary.

On my machine, two background sessions idle for days still have their processes:

  • done/idle since 2026-10-11 22:11 UTC, queued:1, ~430 MB.
  • blocked since 2026-10-08 01:08 UTC, queued:1, still on 2.1.293, ~420 MB.

Settled sessions with queued:0 and comparable idle times were retired on schedule; on 2026-10-13 14:30 UTC the daemon retired four of them, idle 13–16 h, and skipped the first session above.

What Should Happen?

inFlight.queued reports the queue as it is when state.json is written, returning to 0 once the notification is consumed. An idle, settled background session is retired after its grace period.

Error Messages/Logs

state.json of a probe session after its wake-up turn (2.1.296):

"inFlight":{"tasks":0,"queued":1,"kinds":[],"drainableMonitors":0}

A control probe with no background work ended at "queued":0. A probe whose notification was folded into a running turn and that then ran a 25 s foreground command ended at "queued":0: a later task change re-rendered the component.

Steps to Reproduce

  1. Start a background session (claude --bg) and ask it to run sleep 20 with run_in_background: true and end its turn.
  2. When the command finishes, the session wakes up and ends its turn again.
  3. Read ~/.claude/jobs/<id>/state.json: inFlight.queued is 1 and stays 1.
  4. Leave it idle past the retirement grace period: its process keeps running, and claude agents --json keeps listing it with a live pid.

Claude Model

Sonnet

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.301 (code read); measured on 2.1.296

Platform

Anthropic API

Operating System

Other Linux

Terminal/Shell

Other

Additional Information

Arch Linux. #92264 mentions a live job holding {tasks:0, queued:1, kinds:[]} in passing; #99931 and #87891 report background workers that are never retired, which may be this.

View original on GitHub ↗