anthropics / anthropics/claude-code

[BUG] Interactive → background session handoff still leaves title-only .jsonl stubs on 2.1.270–2.1.273 — 7/7 stubs traced to `bg spawned|claimed-spare <id> (slash)` (follow-up to #81662, #85404)

Open
#94,808 0 comments 0 reactions 1 assignee Claimed by @bogini View on GitHub
area:agent-view area:core bug has repro platform:linux
Dominant language
Python
Stars
145k
Forks
23.1k
PR merge metrics
PR metrics pending

Description

### Preflight Checklist

- [x] I have searched existing issues. This is the same bug as #81662 (closed as stale 2026-09-08, bot asked for a new issue if still relevant) and #85404 (closed as completed 2026-08-17; reproductions reported afterwards on 2.1.237 and 2.1.247). Filing new because both are closed and it still reproduces on the current release.
- [x] This is a single bug report.
- [x] I am using the latest version of Claude Code (2.1.273).

### What's Wrong?

When an interactive CLI session is handed off to a background session, the new background session sometimes ends up with a transcript that contains **only** two metadata records (`ai-title`, `agent-name`, ~250 bytes) and no conversation. The original session file gets a `continued-in` record pointing at the new id, so the conversation effectively moves to an empty file. In the resume picker / agents view it shows up as empty or "it has no saved transcript". The conversation itself is intact under the original id.

On one Linux machine this happened **7 times between 2026-08-31 and 2026-09-16**, most recently today on 2.1.273. Every single stub matches, to the millisecond, a daemon handoff logged as `(slash)`:

| handoff (daemon.log) | daemon.log event | original session's `continued-in` record |
|---|---|---|
| 2026-08-31 23:47:40.920Z | `bg spawned (slash)` | none found (older original file) |
| 2026-09-01 21:15:09.583Z | `bg spawned (slash)` | none found (older original file) |
| 2026-09-02 15:38:56.822Z | `bg claimed-spare (slash)` | 15:38:56.833Z |
| 2026-09-08 15:59:30.126Z | `bg claimed-spare (slash)` | 15:59:30.159Z |
| 2026-09-08 17:33:33.881Z | `bg spawned (slash)` | 17:33:33.897Z |
| 2026-09-15 17:38:53.606Z | `bg spawned (slash)` | 17:38:53.712Z |
| 2026-09-16 15:45:50.058Z | `bg spawned (slash)` | 15:45:50.079Z |

So both the fresh-spawn path and the claimed-spare path are affected.

**How the handoff was triggered:** I never typed `/background`. `(slash)` is not specific to that command: in 2.1.273 the dispatcher sets `source: r==="repl" ? "slash" : r`, so every handoff started from the interactive REPL is logged as `slash`. That includes pressing **←** on an empty prompt, which opens the agents view and backgrounds the current conversation. That shortcut is the most likely trigger here: in the five cases where the original transcript records it, the handoff came between 2 minutes and 8 hours after the conversation's last activity, with no command in the transcript. So the bug is easy to hit without realizing a handoff happened at all.

### Anatomy of one occurrence (2.1.270 → 2.1.273, 2026-09-15)

`A` = original interactive session, `B` = background session id created by the handoff.

- `A.jsonl`: 578 records, 251 user/assistant messages, all `version: 2.1.270`. Record 575 is
`{"type":"continued-in","timestamp":"2026-09-15T17:38:53.712Z","sessionId":"A","continuedInSessionId":"B"}`,
followed by `last-prompt` and `cost-state`.
- `B.jsonl`: exactly two lines, 252 bytes: `{"type":"ai-title",...,"sessionId":"B"}` and `{"type":"agent-name",...,"sessionId":"B"}`. No `user`/`assistant`/`system` records.
- `daemon.log`:
```
[2026-09-15T17:38:53.606Z] [bg] bg spawned B (slash)
[2026-09-15T18:38:53.630Z] [bg] bg retire B: idle-prompt, idle 60m
[2026-09-15T18:38:53.705Z] [bg] bg settled B (done)
[2026-09-15T23:38:04.990Z] [bg] bg claimed-spare B (fleet)
```
- Because `A` points to `B` and `B` has no messages, the conversation looks lost from both entries.

### What Should Happen?

The background session should either carry the conversation into `B.jsonl` or keep writing to/resuming `A`. A handoff should never leave a `continued-in` pointer aimed at a transcript with no messages.

### Workaround in use

A systemd timer copies the conversation from `A` into `B` whenever `B` is a title-only stub and something else holds `continued-in → B` (written in compact JSON, because the "has messages" check matches the literal byte string `"type":"user"`). It has recovered all 7 cases with no data loss. `claude --resume A` also works by hand.

### Environment

- Claude Code 2.1.270 / 2.1.273 (native install), daemon backend
- Fedora Linux 44, kernel 7.1, x86_64
- Interactive CLI sessions (`entrypoint: cli`) handed off to the daemon (not via `/background`, see above)

Happy to provide full sanitized daemon.log excerpts or record-level dumps if useful.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.