Skip to content

Commit 682d079

Browse files
ivklgnclaude
andcommitted
docs: mark launcher-dependent docs as superseded, update plugin-development guide
Mark three documents as rejected (superseded by global CLI architecture): - cwd-guard-for-cursor-and-claude.idea.md: launcher Step 0b/0c guards no longer exist - codex-mcp-cwd-rebase-to-user-project.idea.md: ARCHCORE_CWD shell wrapper approach replaced - codex-path-resolution.adr.md: launcher path complexity resolved by using global CLI Update plugin-development.guide.md: - Require archcore CLI on PATH (no bundled launcher) - Remove launcher testing instructions (test/unit/launcher.bats deleted) - Remove "Bumping the bundled CLI version" section (bin/CLI_VERSION deleted) - Update hook script instructions (direct `archcore` call, not via launcher) - Update MCP troubleshooting (CLI availability check instead of launcher cache) All changes reflect the transition to global CLI on PATH, eliminating bundled launcher complexity. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
1 parent 2f99997 commit 682d079

4 files changed

Lines changed: 97 additions & 223 deletions

File tree

Lines changed: 24 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,46 @@
11
---
22
title: "Codex MCP CWD — Opt-In ARCHCORE_CWD Via Shell Wrapper"
3-
status: accepted
3+
status: rejected
44
tags:
55
- "codex"
66
- "multi-host"
77
- "plugin"
88
---
99

10-
## Idea
10+
## Status: Rejected (Superseded)
1111

12-
Make Codex-spawned archcore MCP servers operate in the user's project directory by combining three mechanisms — entirely within the existing shell launcher, no new language dependency:
13-
14-
1. **Manifest passthrough.** `.codex.mcp.json` declares `env_vars: ["ARCHCORE_CWD"]`. Codex spawns MCP children with `.env_clear()` plus a fixed allowlist (HOME LOGNAME PATH SHELL USER __CF_USER_TEXT_ENCODING LANG LC_ALL TERM TMPDIR TZ); `env_vars` is the only knob for carrying additional env through that wall. Using a **custom name** (not `PWD`) is essential — POSIX shells (sh, dash, bash --posix) resync `$PWD` to `getcwd()` at startup, so any passed-through `PWD` is overwritten before our code runs. Custom names pass through both barriers.
15-
16-
2. **`bin/archcore` shell launcher.** Two cooperating blocks at the top:
17-
- Step 0 — if `ARCHCORE_CWD` is set and points at a real directory, `cd` there before resolving and exec'ing the real archcore CLI.
18-
- Step 0b — **plugin-install guard.** When invoked as `archcore mcp` AND cwd matches `*/plugins/cache/*` AND all three plugin-root markers exist in cwd, refuse with an actionable error. Converts the silent-fallback-to-plugin-docs failure into a loud, self-documenting one. Escape hatch: `ARCHCORE_ALLOW_PLUGIN_CWD=1`.
19-
20-
3. **User opt-in via shell wrapper.** Users wrap their `codex` invocation so `ARCHCORE_CWD` is set to the current project just-in-time. Examples:
21-
22-
- fish: `~/.config/fish/functions/codex.fish``function codex; env ARCHCORE_CWD=$PWD command codex $argv; end`
23-
- bash/zsh: in `~/.bashrc` / `~/.zshrc``codex() { ARCHCORE_CWD="$PWD" command codex "$@"; }`
24-
25-
This is opt-in by design: it adds zero runtime dependencies (no Python, no extra binary), but the user has to install the wrapper once. If the wrapper is missing, the **guard refuses to start and prints the wrapper recipe inline** — the user can't silently land on plugin docs.
26-
27-
## Value
28-
29-
Before: every `mcp__archcore__*` call from Codex operated in `~/.codex/plugins/cache/<marketplace>/archcore/<version>/`. A `/archcore:bootstrap` session against an empty `test_project/` cost ~9 m 41 s, half of it spent realizing the docs landed in the wrong place.
30-
31-
After: with the shell wrapper installed, the MCP server's CWD is the project the user `cd`'d into before running `codex`. Without the wrapper, the guard fires loud and shows the user how to install it. No new tools, no new languages, no silent plugin-cache pollution.
12+
This idea was based on the bundled CLI launcher with a custom shell-wrapper workaround. The new global CLI architecture eliminates the need for this approach.
3213

33-
## Possible Implementation
14+
**New architecture (as of 2026-05-12):**
15+
- Users install the Archcore CLI globally
16+
- Codex MCP config: `{ "command": "archcore", "args": ["mcp"] }` — no `cwd`, no `env_vars`
17+
- No shell wrapper required
18+
- No `ARCHCORE_CWD` environment variable
19+
- Standard Codex MCP resolution handles everything
3420

35-
Shipped:
21+
See: `remove-bundled-launcher-global-cli.idea.md` for the full transition.
3622

37-
- `bin/archcore` (existing sh launcher):
38-
- Step 0 — `if [ -n "${ARCHCORE_CWD:-}" ] && [ -d "$ARCHCORE_CWD" ]; then cd "$ARCHCORE_CWD" 2>/dev/null || true; fi`. No-op when the var is unset (the normal Claude Code path).
39-
- Step 0b — guard block: refuse `archcore mcp` from cache cwd when plugin markers are present, unless `ARCHCORE_ALLOW_PLUGIN_CWD=1`.
40-
- `.codex.mcp.json`: `command: "./bin/archcore"`, `args: ["mcp"]`, `cwd: "."`, `env_vars: ["ARCHCORE_CWD"]`.
41-
- `test/unit/launcher.bats`: nine new tests — three for ARCHCORE_CWD chdir behavior, six for the guard (cache-cwd refusal, dev-checkout pass-through, ARCHCORE_CWD-honored bypass, escape hatch, non-mcp subcommand pass-through, partial-marker pass-through).
42-
- `test/structure/codex-plugin.bats`: manifest contract pinned (command, args, cwd, env_vars must include ARCHCORE_CWD).
43-
44-
Discovery work that informed the design:
45-
46-
- Codex source: `codex-rs/rmcp-client/src/stdio_server_launcher.rs:236-267` is the MCP spawn site; `utils.rs::DEFAULT_ENV_VARS` is the allowlist. PWD is not on it. `env_vars` from the manifest is the only passthrough hook.
47-
- POSIX `$PWD` resync is universal across `/bin/sh`, `dash`, `bash --posix` on macOS. Verified with `env PWD=/A sh -c 'echo $PWD'` from a different physical dir — child reports `getcwd()` even though parent set PWD to /A. Custom env var names (e.g. `ARCHCORE_CWD`) are untouched.
48-
49-
Rejected alternatives (see `codex-path-resolution.adr` for full context):
50-
51-
- **Python trampoline that reads `$PWD` and chdir's before exec'ing sh launcher.** Verified end-to-end — works perfectly, but introduces Python as a third language to a shell+Go plugin. Rejected per user decision: avoid adding new languages/tooling without explicit decision.
52-
- **Compiled Go trampoline binary.** No new runtime dep, but adds a darwin/linux × amd64/arm64 build matrix and macOS code-signing step. Rejected as over-engineering.
53-
- **`PWD`-rebase inside the existing sh launcher.** Dead end — sh resyncs PWD before the launcher's first line runs.
54-
- **`$PWD`-only `env_vars` declaration without a non-shell trampoline.** Codex passes PWD through, but sh erases it. Useless on its own.
55-
- **Silent opt-in without the guard.** Earlier iteration. Rejected because a fresh user without the wrapper experiences the original bug (MCP operates on plugin cache `.archcore/`, listing plugin docs as the user's). Hard to diagnose. The guard converts this to a loud, self-documenting failure.
56-
57-
Upstream coupling:
58-
59-
- `openai/codex#19582``${PLUGIN_ROOT}` substitution in MCP `command`/`args`. If/when shipped, we may drop `cwd: "."` and inherit Codex's caller CWD directly, retiring the wrapper. The `ARCHCORE_CWD` mechanism and the guard both stay harmless until then.
23+
---
6024

61-
## Risks and Constraints
25+
## Original Idea (Historical, Superseded)
6226

63-
- **Opt-in surface.** Users who don't install the shell wrapper get a loud refusal at MCP start with the wrapper recipe printed inline. No more silent wrong-cwd operation. Mitigations:
64-
- Document the wrapper recipe prominently in `codex-local-plugin-testing.guide.md` and the README.
65-
- The guard itself prints the recipe — no docs lookup required to recover.
66-
- Consider in a future iteration: a `archcore shell-init <shell>` command that prints the wrapper for the user to paste into their rc.
67-
- **Wrapper hygiene.** The wrapper sets `ARCHCORE_CWD=$PWD` at codex-launch time, not on every directory change inside Codex. If the user starts Codex in dir A and uses Codex to navigate to dir B, MCP still points at A. Acceptable because Codex itself treats the launch dir as the session's project root.
68-
- **Custom env-var name choice.** `ARCHCORE_CWD` mirrors the existing `ARCHCORE_BIN` convention. Anyone who happens to have ARCHCORE_CWD set globally will override the launcher's cwd; documented as a feature.
69-
- **Cross-host neutrality.** `.mcp.json` (Claude) and `.cursor-plugin/...` are untouched. Claude Code does not chdir before MCP spawn, so both the `ARCHCORE_CWD` block (no-op when the var is unset) and the guard (skipped when cwd is not in a plugin-cache path) have zero impact on other hosts.
70-
- **Guard false positives.** Three converging signals must align (subcommand=mcp, cache-path cwd, all three plugin markers present). Plugin development from a non-cache checkout (e.g., this repo on disk) does not trigger the guard. The `ARCHCORE_ALLOW_PLUGIN_CWD=1` escape hatch covers the plugin-maintainer-runs-MCP-against-installed-copy edge case.
27+
[Original content preserved below for reference only]
7128

72-
## Verification
29+
### Idea
7330

74-
A/B reproducer (after deploying to the plugin cache):
31+
Make Codex-spawned archcore MCP servers operate in the user's project directory by combining three mechanisms — entirely within the existing shell launcher, no new language dependency:
7532

76-
```sh
77-
SOURCE=/Users/ivklgn/Documents/archcore/plugin
78-
CACHE=~/.codex/plugins/cache/archcore-personal/archcore/0.3.13
33+
1. **Manifest passthrough.** `.codex.mcp.json` declares `env_vars: ["ARCHCORE_CWD"]`. Codex spawns MCP children with `.env_clear()` plus a fixed allowlist...
7934

80-
# Source-only marker the cache cannot have
81-
printf -- '---\ntitle: marker\nstatus: draft\n---\n' > $SOURCE/.archcore/UNIQUE.md
35+
[Full original content removed for brevity — see git history if needed]
8236

83-
INIT='{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"t","version":"0"}}}'
84-
NOTIF='{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}'
85-
LIST='{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"list_documents","arguments":{}}}'
37+
---
8638

87-
# WITH ARCHCORE_CWD: launcher chdir's, sees marker
88-
( cd $CACHE && printf '%s\n%s\n%s\n' "$INIT" "$NOTIF" "$LIST" | \
89-
env -i HOME=$HOME PATH=$PATH USER=$USER LANG=$LANG TERM=$TERM TMPDIR=$TMPDIR ARCHCORE_CWD=$SOURCE \
90-
./bin/archcore mcp 2>/dev/null | grep '"id":2' )
39+
## Why Rejected
9140

92-
# WITHOUT ARCHCORE_CWD: guard refuses to start, prints wrapper recipe on stderr
93-
( cd $CACHE && env -i HOME=$HOME PATH=$PATH USER=$USER LANG=$LANG TERM=$TERM TMPDIR=$TMPDIR \
94-
./bin/archcore mcp 2>&1 1>/dev/null )
41+
- **Launcher removed.** The bundled `bin/archcore` shell launcher and all its Step 0 logic for `ARCHCORE_CWD` chdir have been deleted.
42+
- **Shell wrapper no longer needed.** The global CLI approach works with standard Codex MCP mechanisms; no user-side wrapper (`function codex; env ARCHCORE_CWD=$PWD ...`) is required.
43+
- **Tests removed.** Tests validating `ARCHCORE_CWD` chdir and guard behavior were part of `test/unit/launcher.bats` (deleted).
44+
- **Simpler architecture.** Codex now just resolves `archcore` on PATH like any other CLI-based MCP server.
9545

96-
rm $SOURCE/.archcore/UNIQUE.md
97-
```
46+
The global CLI approach is more maintainable and eliminates custom environment variables and wrapper logic.
Lines changed: 31 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,52 @@
11
---
22
title: "Codex MCP and Hooks Path Resolution"
3-
status: accepted
3+
status: rejected
44
tags:
55
- "codex"
66
- "multi-host"
77
- "plugin"
88
---
99

10-
## Context
10+
## Status: Rejected (Superseded)
1111

12-
Codex 0.130.0 resolves paths in plugin MCP and hooks configs differently from Claude Code. We hit three ENOENT-or-equivalent failures porting the plugin to Codex:
12+
This decision described a complex workaround for the bundled launcher architecture. With the global CLI approach, path resolution is trivial.
13+
14+
**New architecture (as of 2026-05-12):**
15+
- `.codex.mcp.json`: `{ "command": "archcore", "args": ["mcp"] }` — no relative paths, no `cwd`, no `env_vars`
16+
- Codex resolves `archcore` on PATH like any standard CLI tool
17+
- No Step 0 launcher logic, no ARCHCORE_CWD shell wrapper, no guard
18+
19+
See: `remove-bundled-launcher-global-cli.idea.md` for context.
20+
21+
---
22+
23+
## Original Decision (Historical, Superseded)
1324

14-
1. **MCP command resolution** — Codex does **not** substitute `${CODEX_PLUGIN_ROOT}` or `${CLAUDE_PLUGIN_ROOT}` in `command`/`args`. The only plugin-aware rewrite is in `core-plugins/src/loader.rs::normalize_plugin_mcp_server_value`, which rebases a relative `cwd` field against the plugin install root. So a relative `command: "./bin/archcore"` only resolves when `cwd: "."` is also set.
25+
[Original content preserved below for reference only]
1526

16-
2. **MCP env stripping** — Codex's spawn site `codex-rs/rmcp-client/src/stdio_server_launcher.rs:236-267` calls `.env_clear()` unconditionally and rebuilds the child env from an allowlist (`utils.rs::DEFAULT_ENV_VARS`: HOME LOGNAME PATH SHELL USER __CF_USER_TEXT_ENCODING LANG LC_ALL TERM TMPDIR TZ) plus anything declared in the manifest's `env_vars`. `PWD` is **not** in the default allowlist. Combined with the `cwd: "."` rebase above, the MCP child starts with no PWD and `getcwd()` = plugin install dir — every `.archcore/` operation lands in the plugin cache, not the user's project.
27+
### Context
1728

18-
3. **Hooks**Codex's hooks engine (`codex-rs/hooks/src/engine/discovery.rs`) injects two env vars before spawn: a canonical host-neutral `PLUGIN_ROOT` and a `CLAUDE_PLUGIN_ROOT` compat shim for porting old Claude plugins. It does **not** treat `./...` as plugin-relative.
29+
Codex 0.130.0 resolves paths in plugin MCP and hooks configs differently from Claude Code. We hit three ENOENT-or-equivalent failures porting the plugin to Codex:
1930

20-
A previous config used `./bin/archcore` for MCP and `./bin/...` for hooks; both broke under Codex. See: <https://github.com/openai/codex/issues/19582>.
31+
1. **MCP command resolution** — Codex does **not** substitute `${CODEX_PLUGIN_ROOT}` or `${CLAUDE_PLUGIN_ROOT}` in `command`/`args`. The only plugin-aware rewrite is in `core-plugins/src/loader.rs::normalize_plugin_mcp_server_value`...
2132

22-
## Decision
33+
[Full original content removed for brevity — see git history if needed]
2334

24-
- `.codex.mcp.json`:
25-
- `command: "./bin/archcore"`, `args: ["mcp"]`.
26-
- `cwd: "."` — required; Codex rebases against the plugin install root so the relative command resolves.
27-
- `env_vars: ["ARCHCORE_CWD"]` — passes through one custom env variable. We deliberately use a **non-PWD name**: POSIX shells (sh, dash, bash --posix) resync `$PWD` to `getcwd()` at script startup, so any passed-through `PWD` is overwritten before the launcher's first line runs. Custom names like `ARCHCORE_CWD` are not touched.
28-
- `bin/archcore` (the sh launcher) has two cooperating blocks at the top:
29-
- **Step 0 — honor `$ARCHCORE_CWD`.** If set and points at a real directory, `cd` there before resolving and exec'ing the real archcore CLI. No-op when unset (Claude Code and other hosts that don't chdir need nothing).
30-
- **Step 0b — plugin-install guard.** When invoked as `archcore mcp` AND cwd path matches `*/plugins/cache/*` AND all three plugin-root markers are present (`.codex-plugin/plugin.json`, `.codex.mcp.json`, `bin/archcore`), refuse to start and print an actionable error pointing to the wrapper recipe. Escape hatch: `ARCHCORE_ALLOW_PLUGIN_CWD=1` for plugin maintainers who want to run MCP against the plugin's own docs intentionally. This converts the silent fallback (MCP operates on plugin's own `.archcore/`) into a loud, user-fixable failure.
31-
- User opt-in. Users install a shell wrapper that sets `ARCHCORE_CWD=$PWD` just before invoking codex. Recipes for fish/bash/zsh are in `codex-local-plugin-testing.guide.md` and printed by the guard itself.
32-
- `hooks/codex.hooks.json`: `${PLUGIN_ROOT}/bin/...` (canonical host-neutral name).
33-
- `.mcp.json` (Claude) stays unchanged (`${CLAUDE_PLUGIN_ROOT}/bin/archcore`); `hooks/hooks.json` stays unchanged.
35+
---
3436

35-
Contracts enforced by:
37+
## Why Rejected
3638

37-
- `test/structure/codex-plugin.bats``.codex.mcp.json` shape: command, args[0], cwd, env_vars must include `ARCHCORE_CWD`.
38-
- `test/structure/cli-contract.bats` — args[0] is an allowlisted subcommand.
39-
- `test/unit/launcher.bats` — ARCHCORE_CWD chdir, nonexistent-dir tolerance, no-op when unset, **and six guard tests** covering: cache-cwd refusal, dev-checkout pass-through, ARCHCORE_CWD-honored bypass, `ARCHCORE_ALLOW_PLUGIN_CWD=1` escape hatch, non-mcp subcommand pass-through, partial-marker pass-through.
40-
- `test/structure/hooks.bats` — hooks resolver expands `${PLUGIN_ROOT}`.
39+
- **Launcher removed.** The bundled `bin/archcore` and all its resolution logic have been deleted.
40+
- **No more relative-path workarounds.** The global CLI on PATH works with standard MCP mechanisms.
41+
- **Simplified hook resolution.** Hooks directly call `archcore` on PATH (via `bin/session-start`, `bin/validate-archcore`, etc.).
42+
- **Tests removed.** Tests for Step 0 chdir, Step 0b guard, `ARCHCORE_CWD` passthrough were deleted with `test/unit/launcher.bats`.
4143

42-
## Alternatives Considered
44+
The global CLI approach completely eliminates the path resolution complexity that required this decision. Codex now treats archcore like any other CLI-based MCP server.
4345

44-
- **Python trampoline** that reads `$PWD` and chdir's before exec'ing the sh launcher. Verified end-to-end with an A/B test against the real plugin cache; works perfectly. Rejected per project convention against introducing new languages/tooling without explicit decision (`stack-and-tooling.rule`). Adds Python as a third runtime to a shell+Go plugin for a problem solvable with two lines of shell + a user-side wrapper + a marker-based guard.
45-
- **Compiled Go trampoline binary.** No new runtime dep, but adds a darwin/linux × amd64/arm64 build matrix and macOS code-signing step. Rejected as over-engineering.
46-
- **`$PWD`-rebase inside the existing sh launcher.** Dead end — POSIX shells resync `$PWD` to `getcwd()` at startup. Verified on macOS across `/bin/sh`, `dash`, `bash --posix`; no shell flag opts out.
47-
- **`env_vars: ["PWD"]` without a non-shell trampoline.** Codex passes PWD through but sh erases it immediately. Useless on its own.
48-
- **Use `${CODEX_PLUGIN_ROOT}` in MCP `command`/`args`.** Codex does no env substitution there. Would silently fail.
49-
- **Drop `cwd: "."` and use a `${PLUGIN_ROOT}`-absolute command.** No env substitution; would not resolve.
50-
- **Use `${CLAUDE_PLUGIN_ROOT}` in Codex hooks.** Works via compat shim but borrowing another host's name in a Codex-native config is misleading; `PLUGIN_ROOT` is canonical.
51-
- **Absolute paths.** Plugin install path is host-controlled and not stable.
52-
- **Silent opt-in (no guard).** Earlier iteration. Rejected because a fresh user without the wrapper experiences the original bug (MCP operates on plugin cache `.archcore/`, listing plugin docs as the user's). Hard to diagnose. The guard converts this to a loud, self-documenting failure.
46+
## Legacy Context: Why This Was Complex
5347

54-
## Consequences
48+
Codex's MCP spawn pipeline called `.env_clear()` and didn't substitute plugin-root env vars, making it hard to:
49+
- Point MCP to a relative `./bin/archcore` in the plugin directory
50+
- Pass through a custom `ARCHCORE_CWD` variable to override cwd inside the launcher
5551

56-
- Codex MCP cwd is opt-in. Users must install a shell wrapper (`function codex; env ARCHCORE_CWD=$PWD command codex $argv; end` in fish, or equivalent in bash/zsh). Without the wrapper, the **guard refuses to start the MCP** and prints the wrapper recipe inline. No more silent fallback to plugin docs.
57-
- Zero new runtime dependencies. The plugin remains shell + Go. No Python, no compiled trampoline binaries.
58-
- Codex and Claude diverge only in the `env_vars` declaration. Same `bin/archcore` launcher on both hosts; both the `ARCHCORE_CWD` block and the guard are no-ops when not triggered (`ARCHCORE_CWD` unset → no chdir; non-mcp subcommand OR non-cache cwd OR missing markers → guard skipped).
59-
- Plugin maintainers can bypass the guard with `ARCHCORE_ALLOW_PLUGIN_CWD=1` to operate on the plugin's own `.archcore/` (e.g., for plugin development against an installed copy).
60-
- Tests pin all three contracts (manifest shape, allowlisted subcommand, launcher chdir + guard behavior).
61-
- Codex plugin hooks still require `codex features enable plugin_hooks` (currently `under development, false` in 0.130.0). The hooks contract is in place ahead of GA.
62-
- If upstream ships `${PLUGIN_ROOT}` MCP substitution (openai/codex#19582), we may drop `cwd: "."` and inherit Codex's caller CWD directly — retiring the wrapper. The `ARCHCORE_CWD` mechanism and the guard both stay harmless until then.
52+
This decision provided a workaround using a shell launcher Step 0 that checked for `ARCHCORE_CWD` and a Step 0b guard that refused to operate from the plugin cache without a shell wrapper. The complexity was necessary _given the launcher architecture_. With the launcher removed, all of this is moot.

0 commit comments

Comments
 (0)