github mksglu/context-mode v1.0.124

3 hours ago

v1.0.124

Closes 1 community-reported issue (Pi-side cwd leak that drove a contributor to uninstall) with an architectural fix that replaces hardcoded ban lists with a registry-driven algorithm. The fix protects all 15 adapters symmetrically — adding adapter #16 auto-extends every other adapter's leak protection.

What broke

#545 — Pi-launched ctx_* operates on the wrong repo (CC env leakage)

Reporter @pablopunk: Pi running in ~/.worktrees/fun-project/new-feature, but every ctx_* tool call hit ~/src/my-company/monorepo (his Claude Code main project). ctx_purge "did not work" — because it purged the wrong DB. He uninstalled context-mode after concluding it was broken.

Three parallel ultrathink agents (Pi MCP host researcher reading refs/platforms/oh-my-pi/, codebase auditor, architect) converged on the diagnosis:

  • src/adapters/pi/mcp-bridge.ts:174 spawns server.bundle.mjs via spawn(runtime, [serverScript], { env: childEnv }) with NO cwd: override. The child inherits whatever was in process.env.
  • The user's shell had CLAUDE_PROJECT_DIR=~/src/my-company/monorepo set by a prior claude invocation.
  • src/util/project-dir.ts resolveProjectDir() cascade reads CLAUDE_PROJECT_DIR FIRST → wins.
  • Result: every Pi MCP request resolved project root to Claude's project, not Pi's worktree.

This is CC env leakage poisoning Pi, not "Pi forgot to pass cwd". The Pi MCP host is correct (verified against refs/platforms/oh-my-pi/packages/coding-agent/src/mcp/transports/stdio.ts:51-67). The vulnerability was on our side: the resolver had no notion of "platform-specific own vs foreign env vars".

The architectural fix — algorithmic, all 15 adapters

Hardcoding "Pi ignores CLAUDE/GEMINI/VSCODE env vars" would violate MUST-3 (15 adapters equal) and silently leak through when adapter #16 ships. The fix is a registry-driven algorithm over PLATFORM_ENV_VARS:

  1. Tag every env var entry with role: "workspace" | "identification" (src/adapters/detect.ts). Workspace vars carry a project path; identification vars don't (e.g. CLAUDE_PLUGIN_ROOT).
  2. workspaceEnvVarsFor(p): registry lookup, returns workspace-role entries for platform p.
  3. foreignWorkspaceEnv(p): union of workspace vars for every platform ≠ p. Derived; never hardcoded.
  4. resolveProjectDir({ strictPlatform }): when strictPlatform is set, candidate list = workspaceEnvVarsFor(strictPlatform) ++ ["CONTEXT_MODE_PROJECT_DIR"]. Foreign vars are simply not in the list, so they cannot win, in any order. Non-strict callers preserve the legacy literal cascade bit-for-bit (semver lock).
  5. server.ts getProjectDir() passes strictPlatform: detectPlatform().platform for ALL adapters. Defense in depth — even if a future host leaks foreign env, we already reject it.
  6. Pi bridge env scrub at spawn time uses foreignWorkspaceEnv("pi") to delete leaked vars from the child's env (Pi-specific because it's the only adapter that spawns server.bundle.mjs in-process; verified across all 15 adapter source trees).
  7. resolvePiWorkspaceDir() helper rejects ~/.pi/ (Pi's config dir) as a workspace candidate — unique Pi confusion because PI_CONFIG_DIR was being misused as a workspace signal.
  8. 15×14×3 leak-matrix regression test (tests/util/project-dir-matrix.test.ts) — for every (host, foreign) pair with host ≠ foreign, asserts (a) host's own var wins, (b) result is NEVER the leaked path, (c) CONTEXT_MODE_PROJECT_DIR escape hatch works under every strict mode. 630 assertions from one parameterized test. Adding adapter #16 auto-grows the matrix.

CONTEXT_MODE_PROJECT_DIR remains the universal user override across all platforms (in UNIVERSAL_WORKSPACE_ENV, never banned, never scrubbed).

Why this is the right cut

  • One source of truth. PLATFORM_ENV_VARS becomes the registry for detection AND project-dir cascading AND bridge env scrubbing.
  • MUST-3 satisfied. Strict mode is symmetric across all 15 adapters; Pi-specific code (helper + bridge scrub) only where the bug is Pi-specific.
  • Self-testing. Matrix test covers all current and future adapter pairs without manual extension.
  • Semver-safe. Non-strict callers (e.g. start.mjs entrypoint where detection is unreliable) see zero behavior change.

Tests

  • 3173 pass · 8 pre-existing baseline failures (same OpenCodeAdapter config-paths set verified in v1.0.122 and v1.0.123 — unrelated to this changeset)
  • 4 new test files: tests/adapters/pi-extension-workspace-resolve.test.ts, tests/adapters/pi-mcp-bridge-env-scrub.test.ts, tests/integration/project-dir-strict.test.ts, tests/util/project-dir-matrix.test.ts
  • 10 new tests in tests/adapters/detect.test.ts and tests/util/project-dir.test.ts
  • The matrix test alone generates 630 assertions across the full 15×14 platform grid

Compatibility

15 adapters / 3 OS. No interface changes for any adapter. Strict mode is opt-in via getProjectDir() only — non-strict callers (start.mjs entrypoint, downstream tooling that imports resolveProjectDir directly) see the legacy cascade unchanged. CONTEXT_MODE_PROJECT_DIR continues to override every adapter.

Per-adapter migration (verified):

Adapter Effect of v1.0.124
claude-code Own CLAUDE_PROJECT_DIR always wins; theoretical cross-leak from Gemini/etc now banned
pi #545 fixed. Pi's own PI_WORKSPACE_DIR/PI_PROJECT_DIR win; CC env banned
codex / openclaw / kilo / kiro / zed / antigravity No workspace var registered → falls through to CONTEXT_MODE_PROJECT_DIR → PWD/cwd. Same as today.
cursor / vscode-copilot / jetbrains-copilot / gemini-cli / qwen-code / opencode / omp Own workspace var wins; foreign vars banned. No user-visible change.

Upgrade

npm install -g context-mode@latest
# inside Claude Code:
/ctx-upgrade
# restart your session

Pi users on v1.0.123 or earlier: the upgrade auto-fixes leakage on first MCP boot. No manual cleanup needed.

Thanks

@pablopunk for the staff-grade #434 / #545 reports — explicit reproduction (Pi worktree vs Claude Code worktree, same machine, different adapter, different result), confirmation that uninstall fixed the immediate problem, and patience through the architectural redesign. The "I'll keep this issue open in case someone else runs into it" line is exactly the contributor energy that turns an annoying bug into an architectural cleanup. Hope you give context-mode another try.

Don't miss a new context-mode release

NewReleases is sending notifications on new releases.