v1.0.115 — Hotfix: ctx_stats reads project dir from Claude Code transcripts
A focused follow-up on top of v1.0.113 / v1.0.114. One bug class, one new heuristic, one TDD slice family.
What broke
ctx_stats reported the wrong project directory whenever Claude Code launched the MCP server from a non-project cwd. Symptom: stats output said This conversation started in ~/.claude/plugins/cache/context-mode/context-mode/<version> instead of the user's actual project dir.
This affected every desktop-app launch of Claude Code on macOS, plus every /ctx-upgrade MCP respawn. The previous v1.0.113 fix prevented start.mjs from poisoning the env, and v1.0.114 healed the post-upgrade registry, but both relied on process.env.PWD being a real project dir to recover. When Claude Code is launched from the desktop app, PWD inherits $HOME (not the project), and Claude Code never sets CLAUDE_PROJECT_DIR for MCP spawns. The resolver fell all the way through to the chdir'd plugin dir.
What changed
New resolveProjectDirFromTranscript() heuristic. Claude Code writes per-session transcripts at ~/.claude/projects/<encoded-cwd>/<session-id>.jsonl. Each line is a JSON event; line 2 carries the literal cwd field. The new helper:
- Walks
~/.claude/projects/, finds the most-recently-modified.jsonlacross all session dirs. - Streams only the first 8 KB of the head — never loads the (often 60+ MB) tail into memory.
- Parses up to 10 lines until it finds one with a string
cwdfield. - Returns
undefineddefensively on missing dir / files / malformed JSON — caller falls through.
Multi-window safety: most-recently-modified wins. Answering any tool call writes to the calling window's transcript, so its mtime is freshest. Other Claude Code windows' transcripts have older mtimes and are correctly ignored.
Wired into the resolver chain between env vars and PWD:
- Adapter env vars (rejected if plugin path)
- Transcript heuristic (NEW)
- PWD
- cwd (last resort)
Tests
6 new vertical TDD slices in tests/util/project-dir.test.ts:
- transcript heuristic returns most-recently-modified
cwd - empty / missing dir returns
undefined - transcripts without
cwdfield are skipped - malformed JSON lines don't crash the resolver
resolveProjectDirprefers transcript over PWD when env empty + cwd pluginresolveProjectDirfalls back to PWD when transcript yields nothing
Full suite: 2,816 passed, 8 baseline opencode failures (unrelated), 24 skipped, zero new regressions. Typecheck clean.
Compatibility
15 adapters, 3 OS (macOS / Linux / Windows), no breaking changes, MCP surface unchanged. The transcript heuristic is Claude-Code-specific — other adapters (Codex, Cursor, Gemini, etc.) fall through to their existing env-var resolution.
Upgrade
ctx-upgrade # plugin
npm install -g context-mode # standalone (still triggers v1.0.114 self-heal)
After upgrade, ctx_stats will correctly attribute every session to the project Claude Code is talking about — even when launched from the desktop app where PWD=$HOME.
Cumulative defense (v1.0.113 → v1.0.114 → v1.0.115)
| Layer | Where | What |
|---|---|---|
| start.mjs guard (v1.0.113) | MCP boot | Don't auto-set env from plugin install path |
| Resolver env reject (v1.0.113) | Tool calls | Skip plugin-path env values, prefer PWD |
| Registry self-heal (v1.0.114) | MCP boot + npm postinstall | Sync version mismatch, restore enabledPlugins |
| Upgrade asserts (v1.0.114) | /ctx-upgrade
| Pre-bump verify + post-write consistency check |
| Transcript heuristic (v1.0.115) | Tool calls | Read literal cwd from active session's .jsonl
|