Skip to content

fix: annotate-last resolves wrong session after cd#366

Merged
backnotprop merged 2 commits intobacknotprop:mainfrom
janah01:fix/annotate-last-cwd-mismatch
Mar 22, 2026
Merged

fix: annotate-last resolves wrong session after cd#366
backnotprop merged 2 commits intobacknotprop:mainfrom
janah01:fix/annotate-last-cwd-mismatch

Conversation

@janah01
Copy link
Contributor

@janah01 janah01 commented Mar 22, 2026

Problem

/plannotator-last annotates the wrong message when the user cd's during a Claude Code session. No error, no warning — just the wrong content in the annotation UI.

What happens

findSessionLogsForCwd() builds a project slug from process.cwd(). Claude Code's session log lives under the slug for the original session directory, not the current shell CWD.

  1. Session starts in /Users/me → log stored at ~/.claude/projects/-Users-me/<id>.jsonl
  2. User runs cd ~/projects/my-app
  3. /plannotator-last uses CWD → looks in ~/.claude/projects/-Users-me-projects-my-app/
  4. Finds a stale session from a previous day that happened to use that directory
  5. Opens the annotation UI with that old session's last message

The user has no way to tell something went wrong until they read the text and realize it's not what they expected. By then they may have already written annotations against the wrong content.

Who this affects

Anyone who cd's during a session. That's a common workflow — cd into a repo, run commands, then annotate the assistant's response.

This is a Claude Code-only bug. Codex uses CODEX_THREAD_ID, OpenCode and Pi use their SDK APIs — none of them resolve sessions via CWD. The fix is scoped to the Claude Code else branch; no other harness is touched.

Fix

Three-tier session resolution (most precise first):

1. PPID session metadata (new, primary path)

Claude Code writes ~/.claude/sessions/<pid>.json:

{"pid": 17326, "sessionId": "112c3961-...", "cwd": "/Users/me", "startedAt": ...}

When /plannotator-last runs, its parent process is the Claude Code instance. process.ppid → read the metadata → get the exact session ID and original CWD → open the exact JSONL. O(1), no scanning, no mtime heuristics.

2. CWD slug match (existing behavior, unchanged)

Works when the shell CWD hasn't diverged from the session's project directory.

3. Ancestor walk (new fallback)

Walk up the directory tree trying parent slugs. Handles the case where PPID metadata is unavailable and the user cd'd deeper into a subdirectory.

Other changes

  • getLastRenderedMessage() now catches read errors instead of throwing on missing/corrupt files, so the fallback cascade doesn't crash mid-resolution.
  • Extracted tryLogCandidates() to deduplicate the candidate iteration loop (was copy-pasted between CWD and ancestor fallbacks).

Test plan

  • 44 tests pass (bun test apps/hook/server/)
  • 3 new tests for findSessionLogsByAncestorWalk (root returns empty, walks up to parent, skips exact CWD)
  • This is a Claude Code-only bug. Codex uses CODEX_THREAD_ID, OpenCode and Pi use their SDK APIs — none of them resolve sessions via CWD. The fix is scoped to the Claude Code else branch; no other harness is touched.

When the user cd's during a Claude Code session, /plannotator-last
picks up a stale session log from a different project directory because
findSessionLogsForCwd() uses the current shell CWD, not the session's
original project directory.

Add three-tier session resolution:
1. PPID metadata (~/.claude/sessions/<ppid>.json) for deterministic O(1) lookup
2. CWD slug match (existing behavior, unchanged)
3. Ancestor directory walk as fallback

Also: getLastRenderedMessage() now catches read errors instead of
throwing, and extracted tryLogCandidates() to deduplicate the
candidate iteration loop.
@backnotprop
Copy link
Owner

nice work

@backnotprop
Copy link
Owner

making one adjustment for windows

resolveSessionLogByPpid() was constructing the log path directly,
bypassing the case-insensitive directory scan that findSessionLogsForCwd()
already provides for Windows. Reuse that function instead.

For provenance purposes, this commit was AI assisted.
@backnotprop backnotprop merged commit d3cb2b9 into backnotprop:main Mar 22, 2026
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.

2 participants