claude-code - 💡(How to fix) Fix [BUG] claude --resume picker fails to display custom-title when record is buried mid-file in session JSONL [2 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#53264Fetched 2026-04-26 05:20:10
View on GitHub
Comments
2
Participants
2
Timeline
7
Reactions
0
Author
Timeline (top)
labeled ×5commented ×2

Error Message

Error Messages/Logs

Root Cause

  • #52352 ([BUG] VS Code extension: session rename reverts to auto-generated name from first prompt) — same symptom (display reverts to first-prompt text) but different surface (VS Code) and different root cause (persistence layer doesn't save the rename). This report: rename IS persisted; picker just can't see it.
  • #52411 ([BUG] Chats appear as 'Temporary' when ai-title entry is missing from .jsonl session file) — Desktop app, complementary fallback bug. Confirms ai-title and custom-title are both first-class JSONL records.
  • #53023 (Live session title refresh when hook returns hookSpecificOutput.sessionTitle) — Desktop app, related to title refresh timing rather than picker name extraction.

Code Example



---

TESTDIR="$HOME/cc-rename-bug-test"
mkdir -p "$TESTDIR"

# Generate a UUID via Python (uuidgen isn't always installed on Linux)
SID=$(python3 -c 'import uuid; print(uuid.uuid4())')

# Encode the test directory path the way Claude Code does (/-, leading -)
ENCODED=$(echo "$TESTDIR" | sed 's|/|-|g')
PROJ="$HOME/.claude/projects/$ENCODED"
mkdir -p "$PROJ"

python3 - "$SID" "$TESTDIR" <<'EOF' > "$PROJ/$SID.jsonl"
import json, sys, uuid
from datetime import datetime, timezone
sid = sys.argv[1]
cwd = sys.argv[2]
ts = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%S.000Z')

# Compact JSON, matching the format Claude Code writes natively
def dump(obj):
    return json.dumps(obj, separators=(",", ":"))

def user(content, parent=None):
    return {"type":"user","parentUuid":parent,"isSidechain":False,
      "promptId":str(uuid.uuid4()),"uuid":str(uuid.uuid4()),"timestamp":ts,
      "userType":"external","entrypoint":"cli","cwd":cwd,
      "sessionId":sid, "message":{"role":"user","content":content}}

def assistant(content, parent):
    return {"type":"assistant","parentUuid":parent,"isSidechain":False,
      "uuid":str(uuid.uuid4()),"timestamp":ts,"sessionId":sid,
      "message":{"role":"assistant","content":content}}

opening = user("opening prompt - this will be displayed instead of the title")
print(dump(opening))
print(dump(assistant("ok", opening["uuid"])))

# ~500 KB before the custom-title
for _ in range(100):
    print(dump(user("x" * 5000, opening["uuid"])))

print(dump({"type":"custom-title","customTitle":"REPRO-bug-this-name-should-appear","sessionId":sid}))
print(dump({"type":"agent-name","agentName":"REPRO-bug-this-name-should-appear","sessionId":sid}))

# ~500 KB after the custom-title
for _ in range(100):
    print(dump(user("x" * 5000, opening["uuid"])))
EOF

cd "$TESTDIR" && claude --resume

---

ENCODED=$(echo "$HOME/cc-rename-bug-test" | sed 's|/|-|g')
grep -c '"type":"custom-title"' "$HOME/.claude/projects/$ENCODED/"*.jsonl
# → 1
RAW_BUFFERClick to expand / collapse

Preflight Checklist

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

What's Wrong?

The claude --resume interactive picker fails to display the chosen session name when the latest custom-title record sits more than roughly 64 KB from both ends of the session's JSONL conversation file. The picker appears to read only a small head slice and tail slice of each JSONL when computing display names; if the custom-title record is buried in the middle, the picker can't find it and falls back to displaying the first user-message text in the head slice (e.g. literal strings like "yes", "drop it", or the first prompt body).

The set name is not lost from disk — the custom-title record is still in the JSONL. The bug is in the picker's name-extraction code path only. Selecting the buried-name session from the picker anyway works correctly: Claude Code reads the full JSONL when resuming, the proper name shows in the resumed UI, and claude --resume "<name>" works thereafter.

What Should Happen?

The picker should display the session's custom-title regardless of where the record sits within the JSONL.

Error Messages/Logs

Steps to Reproduce

Note on the repro approach: The script below constructs a session JSONL directly rather than producing one through the natural Claude Code session lifecycle. It does this for determinism and speed: a natural reproduction would require coordinating an external custom-title write into a running session's JSONL, inducing a crash before graceful /exit, or running long enough that periodic anchor writes can't keep up with file growth — none of which compress neatly into a one-shot script. The constructed file is structurally identical to a real Claude Code JSONL: same compact JSON format, same record types, same fields. The picker treats any JSONL under ~/.claude/projects/ as a session regardless of provenance, and the bug exists in the picker's name-extraction logic, which depends only on file contents. Real-world examples of naturally-buried sessions are documented in Additional Information below.

This script synthesizes a session JSONL with a custom-title record buried ~500 KB from each file end — well outside any plausible head/tail window — so the bug reliably triggers regardless of small variations in the picker's exact threshold. Uses $HOME/cc-rename-bug-test rather than /tmp/... to avoid OS-specific symlink quirks (e.g. /tmp/private/tmp on macOS) that would otherwise break the project-dir encoding on Linux.

TESTDIR="$HOME/cc-rename-bug-test"
mkdir -p "$TESTDIR"

# Generate a UUID via Python (uuidgen isn't always installed on Linux)
SID=$(python3 -c 'import uuid; print(uuid.uuid4())')

# Encode the test directory path the way Claude Code does (/ → -, leading -)
ENCODED=$(echo "$TESTDIR" | sed 's|/|-|g')
PROJ="$HOME/.claude/projects/$ENCODED"
mkdir -p "$PROJ"

python3 - "$SID" "$TESTDIR" <<'EOF' > "$PROJ/$SID.jsonl"
import json, sys, uuid
from datetime import datetime, timezone
sid = sys.argv[1]
cwd = sys.argv[2]
ts = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%S.000Z')

# Compact JSON, matching the format Claude Code writes natively
def dump(obj):
    return json.dumps(obj, separators=(",", ":"))

def user(content, parent=None):
    return {"type":"user","parentUuid":parent,"isSidechain":False,
      "promptId":str(uuid.uuid4()),"uuid":str(uuid.uuid4()),"timestamp":ts,
      "userType":"external","entrypoint":"cli","cwd":cwd,
      "sessionId":sid, "message":{"role":"user","content":content}}

def assistant(content, parent):
    return {"type":"assistant","parentUuid":parent,"isSidechain":False,
      "uuid":str(uuid.uuid4()),"timestamp":ts,"sessionId":sid,
      "message":{"role":"assistant","content":content}}

opening = user("opening prompt - this will be displayed instead of the title")
print(dump(opening))
print(dump(assistant("ok", opening["uuid"])))

# ~500 KB before the custom-title
for _ in range(100):
    print(dump(user("x" * 5000, opening["uuid"])))

print(dump({"type":"custom-title","customTitle":"REPRO-bug-this-name-should-appear","sessionId":sid}))
print(dump({"type":"agent-name","agentName":"REPRO-bug-this-name-should-appear","sessionId":sid}))

# ~500 KB after the custom-title
for _ in range(100):
    print(dump(user("x" * 5000, opening["uuid"])))
EOF

cd "$TESTDIR" && claude --resume

Observed: the picker shows opening prompt - this will be displayed instead of the title. Expected: the picker shows REPRO-bug-this-name-should-appear.

Confirm the custom-title record is in fact in the file (works in a fresh shell — uses $HOME and a glob, and locates the project dir by encoding the same way Claude Code does):

ENCODED=$(echo "$HOME/cc-rename-bug-test" | sed 's|/|-|g')
grep -c '"type":"custom-title"' "$HOME/.claude/projects/$ENCODED/"*.jsonl
# → 1

To confirm the picker-vs-resume divergence: select the session anyway. The Claude UI shows REPRO-bug-this-name-should-appear, and claude --resume "REPRO-bug-this-name-should-appear" works correctly thereafter. The fix should be localized to the picker's name-extraction routine; the resume code path already reads the full JSONL and finds the title.

Claude Model

Opus

Is this a regression?

I don't know

Last Working Version

No response

Claude Code Version

2.1.119 (Claude Code)

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

iTerm2

Additional Information

Calibrated picker behavior: head + tail byte windows

Targeted bracket-testing on synthetic sessions with byte-controlled padding before and after the custom-title record narrows the picker's scan to roughly the first ~64 KB and the last ~64 KB of each JSONL. Whichever slice contains the custom-title wins. If the record is outside both, the picker falls back to the first user-message text in the head slice.

WindowLower bound (renders)Upper bound (fails)Estimate
Head (bytes from file start)ct at byte 36 KB → ✅ct at byte 96 KB → ❌~64 KB
Tail (bytes from EOF)ct at byte 48 KB from EOF → ✅ct at byte 80 KB from EOF → ❌~64 KB

The threshold is in bytes, not records — verified by varying filler size while holding line position constant, and vice versa. The estimate is consistent across 30+ synthetic test sessions and 6 confirmed-buried real-world sessions on my machine.

Why graceful exits usually don't trigger this

Claude Code natively writes custom-title records at session start (when -n is used) and as the last record on graceful /exit. (One brief claude -p -n "..." "..." test produced three custom-title records in an 18-record JSONL — start, mid, and end — suggesting Claude does at least sometimes write mid-stream. Whether such writes happen at regular intervals during long interactive sessions is not characterized by this report.) The graceful-exit anchor is what saves most short interactive sessions: the latest custom-title ends up at byte distance 0 from EOF, comfortably inside the tail window.

The bug surfaces when the title isn't anchored to the tail. Three paths can produce that state, the first two being the well-evidenced ones:

  1. Pure-native: /rename mid-stream + crash before next anchor write. A user runs /rename partway through a long session, Claude writes a custom-title record at the byte position of the rename, the session continues for enough turns to push that record out of the tail window, and then the session terminates ungracefully (crash, kill, machine shutdown) before any subsequent anchor write or graceful /exit. The rename record persists in the JSONL but is mid-file; no external tooling involved.

  2. External writes to the JSONL during a running session. If a custom-title record is appended by anything other than the running Claude session itself (a parallel CLI invocation, an SDK call), the live process doesn't pick up the new name. The live process keeps writing whatever name it had in memory (or none), and on graceful /exit writes that — not the externally-set name. The externally-appended record stays wherever it was written, which is mid-file once activity continues past it.

  3. Long sessions where activity outpaces re-anchoring. If Claude doesn't re-emit custom-title records frequently during long interactive sessions, sufficient post-rename activity could push the record out of the tail window before the next write or /exit. This is speculative — the periodic-write cadence in interactive sessions wasn't directly characterized — but worth listing for completeness.

Possible fixes

The team will know best which fits the architecture; any of these would resolve the picker-display path:

  1. Picker reads the whole JSONL when computing display name. Slowest but simplest. May be acceptable since the picker is interactive and not perf-critical.
  2. Picker maintains a persistent external name index keyed by session_id. A persistent on-disk index — distinct from the existing ~/.claude/sessions/*.json PID-style files, which are tied to running processes and don't reliably persist across sessions — would avoid rescanning JSONLs entirely.
  3. Live session re-anchors custom-title more aggressively — every Nth user turn — so the latest record stays inside the tail window even in long sessions.
  4. On session resume, scan the full JSONL for the latest custom-title once and write it to the tail. Cheap one-time cost, fixes buried titles automatically once the user resumes the session.

Related issues

Closest existing issues — distinct from this one but in the same family:

  • #52352 ([BUG] VS Code extension: session rename reverts to auto-generated name from first prompt) — same symptom (display reverts to first-prompt text) but different surface (VS Code) and different root cause (persistence layer doesn't save the rename). This report: rename IS persisted; picker just can't see it.
  • #52411 ([BUG] Chats appear as 'Temporary' when ai-title entry is missing from .jsonl session file) — Desktop app, complementary fallback bug. Confirms ai-title and custom-title are both first-class JSONL records.
  • #53023 (Live session title refresh when hook returns hookSpecificOutput.sessionTitle) — Desktop app, related to title refresh timing rather than picker name extraction.

This issue is specifically about the CLI claude --resume picker, not Desktop or VS Code, with a root cause specific to the picker's narrow head/tail JSONL read.

Cross-machine robustness of the repro

The repro pads the custom-title to ~500 KB from each file end, so even if the picker's window threshold is several times larger than what I measured (~64 KB), the bug should still trigger. To find the exact threshold on a given machine, reduce the range(100) loops in the script and re-run; the title becomes findable once both pads are smaller than the picker's window.

extent analysis

TL;DR

The claude --resume picker fails to display the chosen session name when the custom-title record is more than roughly 64 KB from both ends of the session's JSONL conversation file, and can be fixed by modifying the picker's name-extraction logic to read the full JSONL or maintain a persistent external name index.

Guidance

  • The issue is caused by the picker only reading a small head and tail slice of each JSONL when computing display names, and can be resolved by changing this behavior to read the full JSONL or use an external index.
  • To verify the fix, run the provided repro script and check that the picker displays the correct session name.
  • The fix should be localized to the picker's name-extraction routine, as the resume code path already reads the full JSONL and finds the title.
  • Possible fixes include reading the whole JSONL when computing display name, maintaining a persistent external name index, live session re-anchoring custom-title more aggressively, or scanning the full JSONL for the latest custom-title once on session resume.

Example

No code example is provided as the issue is related to the internal logic of the claude --resume picker and requires changes to the underlying code.

Notes

The exact threshold of the picker's window is not specified, but it is estimated to be around 64 KB based on the provided measurements. The repro script pads the custom-title to ~500 KB from each file end to ensure the bug triggers even if the threshold is larger.

Recommendation

Apply a workaround by modifying the picker's name-extraction logic to read the full JSONL or maintain a persistent external name index, as this will resolve the issue without requiring a full rewrite of the picker's code.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING