[BUG] A single metadata-only stub transcript makes /resume list empty for the ENTIRE project — 33 sessions hidden by one 416-byte file (2.1.210, macOS)
Summary
A single malformed "stub" transcript in ~/.claude/projects/<key>/ causes /resume to
show an empty list for that entire project. Not just the bad session — every
session in the folder disappears from the picker.
In my case one 416-byte file hid 33 healthy sessions, including multi-MB ones
from the same day. Deleting/moving that one file restored the full list immediately.
The transcripts are never damaged. claude --resume <id> restores them perfectly the
whole time — so this is purely a listing/enumeration bug, not data loss. But from the
user's side it is indistinguishable from "my history is gone", which is why I suspect it's
under-reported.
Environment
- Claude Code: 2.1.210 (native install,
~/.local/bin/claude) - OS: macOS (Darwin 25.5.0)
- Shell: zsh
- Not VS Code — plain CLI.
The offending file
A transcript containing only metadata records — no cwd, no timestamp, no user or
assistant messages:
{"type":"ai-title","aiTitle":"connectivity-check","sessionId":"<uuid>"}
{"type":"agent-name","agentName":"connectivity-check","sessionId":"<uuid>"}
{"type":"ai-title","aiTitle":"connectivity-check","sessionId":"<uuid>"}
{"type":"agent-name","agentName":"connectivity-check","sessionId":"<uuid>"}
Mine was written by a daemon-backed background job (template: bg, backend: daemon)
that was spawned but never prompted — its ~/.claude/jobs/<id>/state.json still reads:
state: working
tempo: blocked
needs: send a prompt to start
So the session got named (ai-title/agent-name written) but never had a first turn,
leaving the transcript as pure metadata.
I found the same stub shape in 2 other projects on this machine, with ordinary work titles
("Review fixed code changes", "Change packaged executable naming") — so this is not
specific to background jobs. Any session that is titled and then abandoned before its
first turn appears to produce one.
Symptom
| | |
|---|---|
| /resume in the affected project | empty list (no sessions at all) |
| /resume in a project with no stub | works fine |
| claude --resume <id> --fork-session -p "..." | works — restores the "hidden" session perfectly |
| Transcripts on disk | all intact, valid JSON, correct cwd |
That third row is the key discriminator: resume-by-ID works while the listing is empty.
Storage is fine; the enumerator is what's failing.
Steps to reproduce
- Pick a project with several existing sessions; confirm
/resumelists them. - Write a stub transcript into that project's folder:
UUID=$(uuidgen)
cat > ~/.claude/projects/<encoded-cwd>/$UUID.jsonl <<EOF
{"type":"ai-title","aiTitle":"stub","sessionId":"$UUID"}
{"type":"agent-name","agentName":"stub","sessionId":"$UUID"}
EOF
- Run
/resumein that project → list is empty; all previously-listed sessions gone. - Remove the stub → list returns in full.
Likely cause
The picker appears to read each .jsonl for a sort key (timestamp/mtime) and a filter key
(cwd), and a record possessing neither seems to throw — aborting the whole directory scan
rather than skipping the entry. A malformed or unrecognised transcript should be skipped,
not fatal to its siblings.
Impact
Severity is amplified by how invisible the cause is:
- Presents as total, silent loss of a project's history. Nothing points at a 416-byte file.
- The data is fine, so users may re-do work or abandon context they still have.
- Self-inflicting: any abandoned/never-prompted session poisons the project it was
launched from, and the stub sources (#68435, #60984) are themselves live bugs.
Detection script
Finds stub transcripts across all projects:
python3 -c "
import json,glob,os
for f in glob.glob(os.path.expanduser('~/.claude/projects/*/*.jsonl')):
has_cwd=has_user=False
for line in open(f):
line=line.strip()
if not line: continue
try: d=json.loads(line)
except: continue
if d.get('cwd'): has_cwd=True
if d.get('type')=='user': has_user=True
if not has_cwd and not has_user: print('STUB:', f)
"
Workaround
Move the stub out of the project folder (mv <stub>.jsonl /tmp/). They contain no
conversation, so nothing is lost. The picker recovers instantly — no restart needed.
Suggested fix
Make the enumerator defensive: wrap per-file parsing in a try/catch and skip files with no
usable timestamp/cwd rather than failing the directory. Optionally, don't persist anai-title/agent-name record until a session has its first real turn — that would stop
stubs being created at all.
Related
- #68435 — programmatically-spawned sessions writing
ai-title-only stubs (a source of
the malformed input; that report's symptom is the stub session itself being unresumable,
whereas here the stub takes down every other session in the project).
- #60984 — 2.1.144/145 regression writing
ai-title-only JSONLs (another stub source). - #57203 — "sessions missing from
--resumelist", macOS, notes *"sessions from certain
projects/contexts are not included"*. Undiagnosed, but that per-project pattern is
consistent with this root cause; may be the same bug.