github mksglu/context-mode v1.0.113

latest releases: v1.0.118, v1.0.117, v1.0.116...
4 hours ago

v1.0.113 — Hotfix: /ctx-upgrade no longer poisons project dir

A focused hotfix on top of v1.0.112. One bug, one fix, two TDD slices, defense-in-depth.

What broke

After running /ctx-upgrade, Claude Code killed and respawned the MCP server with cwd set to the plugin install directory (~/.claude/plugins/cache/context-mode/context-mode/<version>/). The legacy start.mjs then unconditionally set CLAUDE_PROJECT_DIR = originalCwd, poisoning every downstream ctx_stats, SessionDB, hash, and event-attribution computation. Sessions silently re-rooted under the plugin install dir instead of the user's actual project. ctx_stats showed "This conversation started in ~/.claude/plugins/cache/context-mode/context-mode/<version>" — wrong path, wrong DB, wrong stats.

This affected every user who upgraded mid-session via /ctx-upgrade since v1.0.107 introduced the in-product upgrade flow.

What changed

Defense-in-depth fix across two layers:

  1. start.mjs — no env poisoning at the source. New isPluginInstallPath() guard checks if originalCwd matches .claude/plugins/(cache|marketplaces)/... (cross-OS regex, POSIX + Windows separators). When true, the env auto-set is skipped — CLAUDE_PROJECT_DIR and CONTEXT_MODE_PROJECT_DIR stay unset rather than carrying a wrong value forward.

  2. server.ts getProjectDir() — defensive resolver. Delegates to a new pure resolveProjectDir({env, cwd, pwd}) helper in src/util/project-dir.ts. The env-var chain (CLAUDE_PROJECT_DIRGEMINI_PROJECT_DIRVSCODE_CWDOPENCODE_PROJECT_DIRPI_PROJECT_DIRIDEA_INITIAL_DIRECTORYCONTEXT_MODE_PROJECT_DIR) now rejects any value matching isPluginInstallPath and falls through to the next source. Before falling back to process.cwd() (which start.mjs chdir'd to the plugin dir), the resolver tries process.env.PWD — shell-set, NOT updated by process.chdir(), so it survives the chdir and points at the user's real session cwd.

The resolver stays total — if PWD is also missing or also a plugin path, it returns cwd rather than throwing, so project-independent tools (sandbox execute, fetch-and-index) keep working. Only project-dependent tools see the degenerate state, and they render gracefully.

Verification

  • Reproducer in pure subprocess form: pre-fix returned a path containing /.claude/plugins/cache/. Post-fix returns the user's project dir when PWD is set (the typical Claude Code MCP restart case).
  • Diagnose discipline: hypotheses ranked before patching, both fix layers verified independently, full repro chain re-run after fix.

Tests

Two new vertical TDD test files:

  • tests/util/project-dir.test.ts — 11 tests for isPluginInstallPath (cross-OS) and resolveProjectDir env-chain semantics.
  • tests/util/start-mjs-no-poison.test.ts — 3 subprocess integration tests verifying start.mjs bootstrap behavior with plugin vs project cwd.

tests/core/server.test.ts updated to pin the new contract: server.ts getProjectDir delegates to resolveProjectDir; the env chain itself is asserted in the shared resolver.

Full suite: 2,787 pass, 24 skipped, zero new regressions. Typecheck clean.

Compatibility

15 adapters, 3 OS (macOS / Linux / Windows), no breaking changes, MCP surface unchanged. The fix is OS-agnostic — the regex matches both / and \ separators.

Upgrade

ctx-upgrade                 # plugin
npm install -g context-mode  # standalone

After upgrade, ctx_stats will correctly show your actual project path even if the previous MCP session had a poisoned env.

Don't miss a new context-mode release

NewReleases is sending notifications on new releases.