Skip to content

fix(sessions): stop co-located sessions from sharing one preview/topic#727

Closed
muqsitnawaz wants to merge 1 commit into
mainfrom
fix-terminal-session-preview
Closed

fix(sessions): stop co-located sessions from sharing one preview/topic#727
muqsitnawaz wants to merge 1 commit into
mainfrom
fix-terminal-session-preview

Conversation

@muqsitnawaz

Copy link
Copy Markdown
Contributor

Bug

agents sessions --active showed the identical preview + topic for every session in one cwd — 6 editor tabs, or two worktree siblings, all rendered the same activity and looked like duplicate cards.

Root cause (debugged + verified by 2 independent reviewers)

findClaudeSessionFile fell through to the newest .jsonl in the cwd whenever a session's <id>.jsonl wasn't found, so N distinct sessions collapsed onto ONE file — and preview/topic derive solely from that file. The id goes stale because an editor caches the launch uuid in live-terminals.json, but Claude rotates its transcript uuid on resume/compact, so the cached id no longer matches any file.

Fix

  • pickSessionFile(dir, id?): a concrete id returns its own file or undefined, never a sibling's; the newest-file fallback is gated to the no-id case.
  • listTerminalsActive resolves each tab's exact id from the pid registry first (mirroring the headless path).
  • classifyActivity: no resolvable file ⇒ idle, not running.
  • Regression test active.pickfile.test.ts (real temp files, no mocks).

Verification

Live fleet: 6 codium tabs went from 6× identical preview → 6× distinct (blank, idle). tsc clean; active + pickfile tests green (7 pass).

Known limitation / follow-up: previews are now blank (not each tab's real content) because the pid registry doesn't cover editor-launched pids and the cached ids are stale. Showing the real per-tab preview needs the upstream fix (editor registers the rotated uuid, or the pid registry covers those pids).

Note: opened during a merge-freeze for the #724 restructure; hold merge until all-clear + rebase onto the new apps/cli/ layout.

`sessions --active` showed the IDENTICAL preview + topic for every session in one
cwd — 6 editor tabs, or two worktree siblings, all looked like duplicate cards.

Root cause: findClaudeSessionFile fell back to the newest `.jsonl` in the cwd
whenever a session's `<id>.jsonl` wasn't found, so N distinct sessions collapsed
onto ONE file (preview + topic derive solely from that file). The id goes stale
because an editor caches the launch uuid in live-terminals.json, but Claude rotates
its transcript uuid on resume/compact — so the cached id no longer matches any file.

- Extract `pickSessionFile(projectDir, id?)`: a concrete id returns its own file or
  undefined, NEVER a sibling's; the newest-file fallback is gated to the no-id case.
- listTerminalsActive resolves each tab's EXACT id from the pid registry first
  (mirroring the headless path), so a tab finds its OWN current transcript.
- classifyActivity: no resolvable file now means `idle`, not `running` (an unfindable
  session isn't evidence of activity — this path fires more now that we don't borrow).
- Test active.pickfile.test.ts (real temp files, no mocks): a missing id yields
  undefined; two distinct missing ids don't collapse; no-id still picks newest.

Verified end-to-end against the live fleet: 6 codium tabs went from 6x identical
preview -> 6x distinct (blank, idle). Note: showing each tab's REAL preview still
needs the upstream stale-id fix (editor should register the rotated uuid, or the pid
registry must cover editor-launched pids) — tracked as follow-up.
@prix-cloud

prix-cloud Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Code Reviewer

Verdict: Ready to merge — but hold per author's own instruction (rebase onto apps/cli/ from #724 first)

Build: tsc — exit 0, no errors. dist/lib/session/active.js freshly emitted.
Tests: 407 run — 399 pass, 8 fail. All 8 failures are pre-existing: 3 in relative-time.test.ts (vi.setSystemTime is a vitest API, bun doesn't have it) and 5 in __tests__/db.test.ts (short_id NOT NULL + home-dir path assertion). Neither file was touched by this PR (last commits: 7d3a8e6, 3c5dce9). New pickfile test: 5/5 pass. Existing active.test.ts: 2/2 pass.

Instructions read: /workspace/task-05788a68/AGENTS.md (root). No per-directory AGENTS.md or CLAUDE.md under src/lib/session/.


Changes that work well

The extraction of pickSessionFile is clean and the exported symbol makes it directly testable. The fix is minimal and surgical — three distinct logical changes, each targeting one of the three places the bug manifested, and the comment on each explains exactly what the pre-fix behavior was. The test file covers the four cases that matter:

  1. concrete id → own file
  2. concrete id absent → undefined, not a sibling (the bug)
  3. two absent ids → both undefined (no collapse)
  4. absent id → newest-file fallback preserved

Real temp files, no mocks — consistent with the repo's "real services only" testing rule.


One thing to verify

sessionId vs sessionFile can diverge when the pid registry resolves a rotated uuid.

In listTerminalsActive (active.ts:450–464):

const resolvedId = readPidSessionEntry(t.pid)?.sessionId ?? t.sessionId;
const sessionFile = findSessionFileForKind(t.kind, t.cwd ?? undefined, resolvedId);
// ...
sessionId: t.sessionId ?? sessionIdFromFile(sessionFile),   // ← still t.sessionId

When the pid registry has a fresh (post-rotate) uuid and findSessionFileForKind returns its .jsonl, the ActiveSession record carries sessionId = t.sessionId (the old stale uuid) while sessionFile points to the new uuid's file. The mismatch means labelMap.get(t.sessionId) may miss a /rename label that was set under the new uuid.

This is a pre-existing pattern (the headless path has the same shape), and the PR description already acknowledges it as a known limitation — upstream fix is the editor registering the rotated uuid. Not a blocker; noting it so it lands in the follow-up ticket.


Merge hold

Per the PR description: hold merge until #724 restructure all-clear + rebase onto the new apps/cli/ layout. This is the author's own note, not a review finding — surfacing it here so it isn't missed in the merge queue.


Reviewed by Code Reviewer — actually ran the build and tests on this branch.

@muqsitnawaz

Copy link
Copy Markdown
Contributor Author

Superseded by #728 — the identical fix rebuilt on the post-#724 apps/cli/ layout (this branch predates the restructure and now conflicts). Landing #728 instead.

@muqsitnawaz muqsitnawaz closed this Jul 7, 2026
muqsitnawaz added a commit that referenced this pull request Jul 7, 2026
#728)

* fix(sessions): stop co-located sessions from sharing one preview/topic

`sessions --active` showed the IDENTICAL preview + topic for every session in one
cwd — 6 editor tabs, or two worktree siblings, all looked like duplicate cards.

Root cause: findClaudeSessionFile fell back to the newest `.jsonl` in the cwd
whenever a session's `<id>.jsonl` wasn't found, so N distinct sessions collapsed
onto ONE file (preview + topic derive solely from that file). The id goes stale
because an editor caches the launch uuid in live-terminals.json, but Claude rotates
its transcript uuid on resume/compact.

- Extract `pickSessionFile(projectDir, id?)`: a concrete id returns its own file or
  undefined, NEVER a sibling's; the newest-file fallback is gated to the no-id case.
- listTerminalsActive resolves each tab's EXACT id from the pid registry first
  (mirroring the headless path).
- classifyActivity: no resolvable file now means `idle`, not `running`.
- Test active.pickfile.test.ts (vitest, real temp files, no mocks).

Verified end-to-end pre-restructure: 6 codium tabs went from 6x identical preview
-> 6x distinct (blank, idle). Rebuilt on the apps/cli layout after #724.

Supersedes #727 (which targeted the pre-restructure src/ path).

* docs(sessions): correct pid-registry comment — no-op for editor shell pids

Reviewer flagged the comment overstated the registry lookup: live-terminals.json
stores the shell pid but the by-pid registry is keyed by the agent pid, so for
editor-launched terminals the lookup returns undefined and falls back to the
cached id. The duplicate-card fix is pickSessionFile no longer borrowing a
sibling. Comment now says so; no behavior change.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant