[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)

Open 💬 0 comments Opened Jul 15, 2026 by purplemass

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

  1. Pick a project with several existing sessions; confirm /resume lists them.
  2. 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
  1. Run /resume in that project → list is empty; all previously-listed sessions gone.
  2. 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 an
ai-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 --resume list", 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.

View original on GitHub ↗