Background launch provisions a second job identity with no spec or prompt, and publishes it in ListAgents as an addressable idle peer
What's Wrong?
Launching a single background job provisions two job identities about 33 ms apart. Only one of them ever receives a spec and a prompt. The other gets a job directory and a pre-warmed claude bg-spare worker, and then sits with respawnFlags: [], no state.json, no timeline.jsonl and no transcript. It has never been given any task at all.
That empty identity is nevertheless published to other sessions: ListAgents lists it as bg · idle, formatted identically to sessions that are doing real work, and it is addressable by SendMessage.
When a peer session addresses it, the empty identity wakes up, materialises a transcript, auto-names itself from the incoming message, and answers as a normal session.
This is a correctness problem for cross-session coordination, not just list noise. Sessions here use ListAgents + SendMessage as a lock-announcement protocol ("I am about to write to file X: are you holding it?"). A never-used slot answers that question truthfully about itself ("I am not holding it") while being structurally incapable of holding anything, and the answer is indistinguishable from a real all-clear given by the session that actually works in that directory. The sender cannot tell the two apart from the ListAgents output, because the empty slot and a genuinely idle worker render the same.
What Should Happen?
Either of these would fix it:
- Do not publish job identities that have never been given a spec or a prompt. Keep them out of
ListAgentsuntil they are claimed for real work. - Or distinguish them in the output, e.g.
unclaimed/no taskinstead ofidle, so a peer can tell "idle because it finished its work" from "idle because it never had any".
Secondary, lower severity, same episode. When a session's very first prompt is an inbound cross-session message, the auto-generated display name is derived from that message. ListAgents then shows the sender a name that paraphrases the sender's own announcement, which reads as confirmation that the right session picked the message up. Auto-naming from the first prompt is reasonable in general (a session started from a human prompt gets its name the same way, and that is fine), but combined with defect 1 it makes a session that has never touched the topic advertise that exact topic back to the peer that asked about it.
Error Messages/Logs
No error is emitted; everything below is normal-path state on disk. CLI 2.1.234, Linux, daemon backend.
One background job was launched from an interactive session at 09:51:49. Two job dirs were created, 33 ms apart:
$ stat -c 'birth=%w %n' ~/.claude/jobs/*
birth=2026-08-18 09:51:49.563443372 -0300 /home/<user>/.claude/jobs/<A>
birth=2026-08-18 09:51:49.596443441 -0300 /home/<user>/.claude/jobs/<B> # never used
48 minutes later, <B> still had nothing but an empty tmp/:
$ ls -la --time-style=full-iso ~/.claude/jobs/<B>/
drwxrwxr-x tmp/ 2026-08-18 09:51:49.596 # created at job birth, never modified again
# no state.json, no timeline.jsonl, no transcript under ~/.claude/projects/
The two identities differ exactly as you would expect from "one was launched, one was not":
<A>: respawnFlags=['--agent','claude','--permission-mode','auto','--model','opus[1m]'] intent="<the human prompt>"
<B>: respawnFlags=[] intent=<absent until a peer wrote to it>
Despite that, a third session's ListAgents at 10:39:08, 48 seconds before any message reached <B>, already listed it:
Peer sessions (4):
<user>-62 [b6b441] · interactive · idle · started 3h ago
<A-name> [888a84] · bg · busy · started 47m ago
<B> [f573d8] · bg · idle · started 47m ago
...
<B> is listed by its raw job id because it has no name yet: it has never had a prompt. The peer had no way to see that, picked it as a coordination target, and sent it a lock announcement at 10:39:56. What followed:
10:39:56.444 transcript file for <B> created (exact second the peer's SendMessage landed)
10:40:09.947 ~/.claude/jobs/<B>/timeline.jsonl created; first entry state="working"
state.json now carries "nameSource": "auto" with a name paraphrasing the peer's message
10:41:00 <B> answers the peer, truthfully, that it is not holding the file
Minor detail that looks like a timeline inconsistency but is not: the transcript file's birth is 10:39:56 while its earliest content timestamp is 09:51:53. The session context is assembled at job-creation time and only flushed to disk when a first prompt arrives.
Steps to Reproduce
- On Linux, with the background/agents daemon backend, launch a single background job from an interactive session (any prompt).
ls -la --time-style=full-iso ~/.claude/jobs/. Two job directories appear within ~35 ms of each other. Only one getsstate.jsonandtimeline.jsonl.- From a different session, call
ListAgents. The unused id is listed asbg · idle, addressable, and rendered the same as a real worker. SendMessageto that id. It wakes, creates a transcript, auto-names itself from the text you sent, and replies as a normal session.
Claude Code Version
2.1.234
Is this a regression?
Not sure. This host has not observed the previous behaviour, so no last-working version can be named.
Platform / OS / Terminal
Claude subscription; Linux (Ubuntu 24.04.4, kernel 7.0.0); Ghostty.
Related issues (not duplicates)
- #82107: background daemon spare roster advertising an already-reaped spare. Same daemon/spare area, opposite condition: that one is a dead worker being claimed, this one is a live worker that was never given a task.
- #85497, #84945: cross-session peer socket failing to bind. Also about
ListAgents/SendMessagereachability, but those are sessions that should be reachable and are not; this is an identity that should not be offered and is.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗