[BUG] In-session /resume picker collapses to ~0 rows inside a git worktree and never recovers (Ctrl+W restores; `claude -r` from the shell is unaffected)
Summary
In a repo with multiple git worktrees, the /resume picker invoked from inside a running session paints the full session list, then ~20 ms later collapses to 2–3 rows and stays that way indefinitely (still collapsed at t=30 s). The header loses its (1 of N) counter while collapsed, which is the visible tell.
The same picker launched as claude -r from a plain shell, in the same directory, does not collapse — it briefly passes through the same state and recovers within ~30 ms, showing the full list.
Pressing <kbd>Ctrl</kbd>+<kbd>W</kbd> ("show all worktrees") in the collapsed picker restores the full list instantly, which points at the current-worktree filter rather than at loading or at the transcripts themselves.
The transcripts are all intact on disk. In my case 41 sessions for the current branch, none older than 20 days, were invisible in /resume — easy to misread as the 30-day cleanupPeriodDays cleanup having eaten them.
Environment
- Claude Code 2.1.232 (native install)
- Linux 6.8.0-136-generic, zsh, xterm-256color
- Repo with 13 git worktrees (
git worktree list); session started in a linked worktree, not the main one - 69 project dirs / 578
.jsonltranscripts under~/.claude/projects - 111 transcripts in the current worktree's project dir, 41 of them on the current branch
Reproduction
Needs a repo with more than one git worktree and a handful of past sessions in the current one.
cdinto a linked worktree of that repo- Start Claude normally:
claude - Run
/resume - Watch the list
Automated repro (drives both paths in a pty and prints header + row count over 30 s; needs pip install pyte):
<details>
<summary><code>resume-worktree-filter.py</code></summary>
#!/usr/bin/env python3
"""
Repro: in-session /resume collapses the session list when the current-worktree
filter is active, and never recovers. `claude -r` from a plain shell does not.
Usage: python3 resume-worktree-filter.py [/path/to/a/git/worktree]
Requires: pyte (pip install pyte)
"""
import os, sys, pty, time, select, signal, struct, fcntl, termios
try:
import pyte
except ImportError:
sys.exit("pip install pyte")
CWD = sys.argv[1] if len(sys.argv) > 1 else os.getcwd()
COLS, ROWS = 150, 45
def run(argv, feed_resume):
screen = pyte.Screen(COLS, ROWS)
stream = pyte.ByteStream(screen)
pid, fd = pty.fork()
if pid == 0:
os.chdir(CWD)
os.environ["TERM"] = "xterm-256color"
os.execvp(argv[0], argv)
fcntl.ioctl(fd, termios.TIOCSWINSZ, struct.pack("HHHH", ROWS, COLS, 0, 0))
def drain(seconds):
end = time.time() + seconds
while time.time() < end:
r, _, _ = select.select([fd], [], [], 0.1)
if r:
try:
d = os.read(fd, 65536)
except OSError:
return
if not d:
return
stream.feed(d)
def header():
for line in screen.display:
if "Resume session" in line:
return line.strip()
return "<no picker>"
def rows():
return sum(1 for l in screen.display if " ago \xb7 " in l or " ago · " in l)
if feed_resume:
drain(14)
os.write(fd, b"/resume")
drain(1.5)
os.write(fd, b"\r")
for t in (2, 5, 10, 20, 30):
drain(t - (0 if t == 2 else prev))
prev = t
print(f" t={t:>2}s header={header()!r} visible_rows={rows()}")
os.write(fd, b"\x17") # Ctrl+W -> show all worktrees
drain(4)
print(f" after Ctrl+W header={header()!r} visible_rows={rows()}")
os.write(fd, b"\x1b")
drain(1)
os.write(fd, b"\x1b")
drain(0.5)
try:
os.kill(pid, signal.SIGKILL)
except Exception:
pass
print(f"cwd: {CWD}")
print("\nA) interactive session, then /resume (expected: list collapses and stays collapsed)")
run(["claude"], feed_resume=True)
print("\nB) claude -r from the shell (expected: list populates normally)")
run(["claude", "-r"], feed_resume=False)
</details>
Observed
cwd: /home/user/projects/myrepo/wt10
A) interactive session, then /resume
t= 2s header='Resume session' visible_rows=3
t= 5s header='Resume session' visible_rows=3
t=10s header='Resume session' visible_rows=3
t=20s header='Resume session' visible_rows=3
t=30s header='Resume session' visible_rows=3
after Ctrl+W header='Resume session (1 of 50)' visible_rows=10
B) claude -r from the shell
t= 2s header='Resume session (1 of 51)' visible_rows=11
t= 5s header='Resume session (1 of 51)' visible_rows=11
t=10s header='Resume session (1 of 51)' visible_rows=11
t=20s header='Resume session (1 of 51)' visible_rows=11
t=30s header='Resume session (1 of 51)' visible_rows=11
after Ctrl+W header='Resume session (1 of 116)' visible_rows=11
Rendered frames from the in-session run, ~20 ms apart. Full list first:
Resume session (1 of 50)
╭──────────────────────────────────────────────────────────────╮
│ ⌕ Search… │
╰──────────────────────────────────────────────────────────────╯
❯ Investigate old session deletion timeline
10 seconds ago · bg · topic/ABC-1001/security-review · 612.8KB
Investigate old session deletion timeline
11 minutes ago · topic/ABC-1001/security-review · 290.8KB
wt7: implementation
16 hours ago · topic/ABC-1002/rename-report-columns · 2.4MB
wt8: plan
17 hours ago · topic/ABC-1003/enforce-editability-on-create · 1.1MB
... 6 more rows ...
then, immediately after, and unchanged for the next 30 s:
Resume session
╭──────────────────────────────────────────────────────────────╮
│ ⌕ Search… │
╰──────────────────────────────────────────────────────────────╯
myrepo · wt10
❯ Investigate old session deletion timeline
10 seconds ago · bg · topic/ABC-1001/security-review · 612.8KB
Investigate old session deletion timeline
11 minutes ago · topic/ABC-1001/security-review · 290.8KB
Ctrl+A to show all projects · Ctrl+B to only show current branch · Ctrl+W to show all worktrees · …
What started this investigation was the same collapsed state with <kbd>Ctrl</kbd>+<kbd>B</kbd> (current branch only) also active, where it rendered with zero rows: an entirely empty picker, scope chip myrepo · topic/ABC-1001/security-review · wt10, no sessions at all. For comparison, claude -r in the same directory with the identical scope chip lists 29 sessions.
Expected
The current-worktree filter should not remove sessions that belong to the current worktree, and the picker should not stay in a partially-filtered state. Either the collapsed render should be skipped until the metadata the filter depends on is available, or it should recover when that metadata arrives — as the claude -r path does.
Notes
Two things suggest the filter is applied against session metadata that hasn't been resolved yet, rather than the filter predicate itself being wrong:
- <kbd>Ctrl</kbd>+<kbd>W</kbd> (bypassing the worktree filter) restores the list instantly and permanently.
- In the shell (
claude -r) path, the identical collapsed frame appears and then recovers ~30 ms later, i.e. the same code path can complete — it just doesn't when the picker is opened from inside a session.
Mechanically similar to #78918 (a picker filter applied to pages before they are fully populated).
The missing (1 of N) counter in the header is a reliable indicator that the visible list is not the real result set — it may be worth not rendering the list at all in that state.
Workarounds
- <kbd>Ctrl</kbd>+<kbd>W</kbd> in the picker
claude -r/claude -r "<search term>"from the shell instead of/resumeclaude -r <session-id>directly
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗