feat: port claude-jail upgrades — arg-parsing lock, pruning ignore walk, /opt/mcp + --mcp-image #3

Merged
claudiomaradonna merged 5 commits from feat/upstream-2026-08-25 into main 2026-08-25 15:04:47 +02:00

Brings codex-jail up to date with claude-jail through a3d865c (2026-08-25). Five commits, one per upstream feature, each with its tests.

Position-anchored workspace parsing (claude-jail c0c8d87 + 62d22a3)

A bare word can claim the workspace only while the wrapper is still parsing its own arguments; anything bound for codex locks the slot. codex . -p docs now mounts . and forwards -p docs instead of
silently mounting ./docs. A -- is consumed as the wrapper boundary while the slot is open and forwarded to codex once it is locked, so codex . mcp add srv --env K=V -- /opt/mcp/srv reaches the CLI
intact. Two bare directories are a hard error naming both fixes, and the resolved workspace is printed to stderr. Spec ported to docs/superpowers/specs/2026-08-25-argument-parsing-design.md.

.codexignore pruning find walk (claude-jail a92e6c6)

Patterns become path-aware EREs fed to a single find walk that -prunes at each matching directory: hiding node_modules/ costs one mount and the walk never enters it, where the old globstar
expansion re-walked the whole tree per pattern and could push podman's argv past ARG_MAX. Also fixes the inherited defects: a pattern containing a space matched nothing, and trailing whitespace was
stripped one character at a time. Known divergence (documented, pinned by a test): a pattern segment no longer reaches through a symlinked directory.

Shared MCP servers: /opt/mcp + --mcp-image (claude-jail 340140c + a3d865c)

~/.codex-jail/mcp is mounted read-only at /opt/mcp in every container, so codex mcp add <name> -- /opt/mcp/<server> resolves in every workspace without depending on CODEX_JAIL_MOUNTS.
--mcp-image <ref> (or CODEX_JAIL_MCP_IMAGES, comma-separated) fills the directory from OCI images whose entrypoint is a single static binary (e.g. ghcr.io/mudler/mcps/*): pulled every run so
:latest tracks upstream, extracted from a created-but-never-started container, refreshed only when the image id moved, temp-file + rename so a concurrently executing binary is never written in place,
pull failures fall back to the cached copy, hand-dropped binaries are never clobbered, and --dry-run narrates instead of touching podman.

Divergence from upstream, by design: codex-jail mounts the whole jail directory read-write at /home/codex/.codex, which claude-jail does not — an unprotected mcp/ would let the agent rewrite a
server binary that every future session, in any workspace, executes. The directory is therefore seeded on first run and its read-write view is hidden behind an empty tmpfs, exactly like the existing
env.default mask; binaries are reachable only through the read-only /opt/mcp. A stray non-directory at ~/.codex-jail/mcp fails closed with a clear error.

Catch-ups (claude-jail 3503587, f61edc2, c589ca2)

Dockerfile pre-creates ~/.cache/ssh for the SSH ControlPath socket; .gitignore gains the local-Claude-settings and worktrees entries; .env.example documents CODEX_JAIL_NETWORK (already supported
by script and README) and CODEX_JAIL_MCP_IMAGES.

Testing

The suite grows from 37 to 106 tests: test_arg_parsing.sh (19), test_codexignore.sh (27), test_mcp_dir.sh (7), test_mcp_images.sh (16). The podman stub gains simulated pull/image inspect/create/cp/rm subcommands and a call journal (STUB_CALLS_FILE) so the extraction flow runs end to end. New-behaviour tests were watched fail before each implementation; bash tests/run.sh → ALL TESTS PASSED, plus a real --dry-run verified the new mounts and stderr diagnostics by hand.

Brings codex-jail up to date with claude-jail through a3d865c (2026-08-25). Five commits, one per upstream feature, each with its tests. ## Position-anchored workspace parsing (claude-jail c0c8d87 + 62d22a3) A bare word can claim the workspace only while the wrapper is still parsing its own arguments; anything bound for codex locks the slot. `codex . -p docs` now mounts `.` and forwards `-p docs` instead of silently mounting `./docs`. A `--` is consumed as the wrapper boundary while the slot is open and forwarded to codex once it is locked, so `codex . mcp add srv --env K=V -- /opt/mcp/srv` reaches the CLI intact. Two bare directories are a hard error naming both fixes, and the resolved workspace is printed to stderr. Spec ported to `docs/superpowers/specs/2026-08-25-argument-parsing-design.md`. ## `.codexignore` pruning find walk (claude-jail a92e6c6) Patterns become path-aware EREs fed to a single `find` walk that `-prune`s at each matching directory: hiding `node_modules/` costs one mount and the walk never enters it, where the old globstar expansion re-walked the whole tree per pattern and could push podman's argv past ARG_MAX. Also fixes the inherited defects: a pattern containing a space matched nothing, and trailing whitespace was stripped one character at a time. Known divergence (documented, pinned by a test): a pattern segment no longer reaches through a symlinked directory. ## Shared MCP servers: `/opt/mcp` + `--mcp-image` (claude-jail 340140c + a3d865c) `~/.codex-jail/mcp` is mounted read-only at `/opt/mcp` in every container, so `codex mcp add <name> -- /opt/mcp/<server>` resolves in every workspace without depending on `CODEX_JAIL_MOUNTS`. `--mcp-image <ref>` (or `CODEX_JAIL_MCP_IMAGES`, comma-separated) fills the directory from OCI images whose entrypoint is a single static binary (e.g. `ghcr.io/mudler/mcps/*`): pulled every run so `:latest` tracks upstream, extracted from a created-but-never-started container, refreshed only when the image id moved, temp-file + rename so a concurrently executing binary is never written in place, pull failures fall back to the cached copy, hand-dropped binaries are never clobbered, and `--dry-run` narrates instead of touching podman. **Divergence from upstream, by design:** codex-jail mounts the whole jail directory read-write at `/home/codex/.codex`, which claude-jail does not — an unprotected `mcp/` would let the agent rewrite a server binary that every future session, in any workspace, executes. The directory is therefore seeded on first run and its read-write view is hidden behind an empty tmpfs, exactly like the existing `env.default` mask; binaries are reachable only through the read-only `/opt/mcp`. A stray non-directory at `~/.codex-jail/mcp` fails closed with a clear error. ## Catch-ups (claude-jail 3503587, f61edc2, c589ca2) Dockerfile pre-creates `~/.cache/ssh` for the SSH ControlPath socket; `.gitignore` gains the local-Claude-settings and worktrees entries; `.env.example` documents `CODEX_JAIL_NETWORK` (already supported by script and README) and `CODEX_JAIL_MCP_IMAGES`. ## Testing The suite grows from 37 to 106 tests: `test_arg_parsing.sh` (19), `test_codexignore.sh` (27), `test_mcp_dir.sh` (7), `test_mcp_images.sh` (16). The podman stub gains simulated `pull`/`image inspect`/`create`/`cp`/`rm` subcommands and a call journal (`STUB_CALLS_FILE`) so the extraction flow runs end to end. New-behaviour tests were watched fail before each implementation; `bash tests/run.sh` → ALL TESTS PASSED, plus a real `--dry-run` verified the new mounts and stderr diagnostics by hand.
Adapted from claude-jail 2bf7aef + 91c5912 (contract amendment: a locked
-- belongs to codex).

Claude-Session: https://claude.ai/code/session_0166CnS6KiUy1ZyMdgVGMemy
Port of claude-jail c0c8d87 + 62d22a3. A bare word may claim the
workspace only while the wrapper is still parsing its own arguments;
anything bound for codex locks the slot, so an option value ('codex .
-p docs') can no longer be mounted by accident. -- is consumed as the
boundary while the slot is open and forwarded to codex once it is
locked, so 'codex . mcp add srv --env K=V -- /path' reaches the CLI
intact. Two bare directories are an error naming both fixes, and the
resolved workspace is printed to stderr.

Adds tests/test_arg_parsing.sh: 9 tests for the new behaviour and 10
pinning documented invocations (watched fail before the change).

Claude-Session: https://claude.ai/code/session_0166CnS6KiUy1ZyMdgVGMemy
Port of claude-jail a92e6c6. Each pattern was expanded with bash
globstar, once per pattern; bash cannot prune, so every pattern
re-walked the whole workspace including the subtrees the ignore file
itself hides, and a broad pattern could push podman's argv past
ARG_MAX. Patterns now become path-aware regexes fed to find(1), which
prunes at each matching directory and never emits a mount inside an
already-hidden subtree.

Also fixes the defects the old form carried: a pattern containing a
space was word-split and matched nothing, and trailing whitespace was
stripped one character at a time. The known divergence is inherited:
a pattern segment no longer reaches through a symlinked directory
(documented in README, pinned by a test).

Adds tests/test_codexignore.sh (27 tests, watched fail on the four
defect cases first); the feature had none.

Claude-Session: https://claude.ai/code/session_0166CnS6KiUy1ZyMdgVGMemy
Port of claude-jail 340140c + a3d865c. ~/.codex-jail/mcp holds MCP
server binaries shared across all projects, mounted read-only at
/opt/mcp so 'codex mcp add <name> -- /opt/mcp/<server>' resolves in
every session without depending on CODEX_JAIL_MOUNTS. --mcp-image <ref>
(or CODEX_JAIL_MCP_IMAGES, comma-separated) fills it from OCI images
whose entrypoint is a single static binary: pulled every run, extracted
from a created-but-never-started container, refreshed only when the
image id moved, temp-file + rename so a concurrently executing binary
is never written in place, pull failures fall back to the cached copy,
and hand-dropped binaries are never clobbered.

Codex-specific: the whole jail directory is bind-mounted read-write at
/home/codex/.codex, so the mcp directory is seeded on first run and
that read-write view is hidden behind an empty tmpfs — the same
reasoning as the env.default mask; without it the read-only /opt/mcp
would be a fiction and a rewritten binary would persist into every
future session. A stray non-directory at ~/.codex-jail/mcp is a hard
error instead of a cryptic podman failure.

The podman test stub grows simulated pull/inspect/create/cp/rm
subcommands and a call journal (STUB_CALLS_FILE); adds
tests/test_mcp_dir.sh (7) and tests/test_mcp_images.sh (16), watched
fail before the implementation.

Claude-Session: https://claude.ai/code/session_0166CnS6KiUy1ZyMdgVGMemy
Port of claude-jail 3503587 (pre-create ~/.cache/ssh so the first ssh
connection can bind its control socket), f61edc2 (ignore local Claude
settings and worktrees), and the c589ca2 .env.example entry for
CODEX_JAIL_NETWORK, which the script and README already supported;
documents CODEX_JAIL_MCP_IMAGES alongside it.

Claude-Session: https://claude.ai/code/session_0166CnS6KiUy1ZyMdgVGMemy
claudiomaradonna deleted branch feat/upstream-2026-08-25 2026-08-25 15:04:49 +02:00
Commenting is not possible because the repository is archived.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
claudiomaradonna/codex-jail!3
No description provided.