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:174spawnsserver.bundle.mjsviaspawn(runtime, [serverScript], { env: childEnv })with NOcwd:override. The child inherits whatever was inprocess.env.- The user's shell had
CLAUDE_PROJECT_DIR=~/src/my-company/monoreposet by a priorclaudeinvocation. src/util/project-dir.ts resolveProjectDir()cascade readsCLAUDE_PROJECT_DIRFIRST → 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:
- 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). workspaceEnvVarsFor(p): registry lookup, returns workspace-role entries for platformp.foreignWorkspaceEnv(p): union of workspace vars for every platform ≠p. Derived; never hardcoded.resolveProjectDir({ strictPlatform }): whenstrictPlatformis 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).server.ts getProjectDir()passesstrictPlatform: detectPlatform().platformfor ALL adapters. Defense in depth — even if a future host leaks foreign env, we already reject it.- 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 spawnsserver.bundle.mjsin-process; verified across all 15 adapter source trees). resolvePiWorkspaceDir()helper rejects~/.pi/(Pi's config dir) as a workspace candidate — unique Pi confusion becausePI_CONFIG_DIRwas being misused as a workspace signal.- 15×14×3 leak-matrix regression test (
tests/util/project-dir-matrix.test.ts) — for every(host, foreign)pair withhost ≠ foreign, asserts (a) host's own var wins, (b) result is NEVER the leaked path, (c)CONTEXT_MODE_PROJECT_DIRescape 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_VARSbecomes 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.mjsentrypoint 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.tsandtests/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 sessionPi 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.