Skip to content

Let multi-repo and repo-less Codex runs start - #2069

Open
ppXD wants to merge 1 commit into
mainfrom
fix/let-a-multi-repo-codex-run-start
Open

ppXD wants to merge 1 commit into
mainfrom
fix/let-a-multi-repo-codex-run-start

Conversation

@ppXD

@ppXD ppXD commented Oct 4, 2026

Copy link
Copy Markdown
Owner

Summary

  • A Codex run whose cwd is not inside a git repository could not start. Codex 0.142.2 exits 1 before any model request ("Not inside a trusted directory and --skip-git-repo-check was not specified"), for exec and exec resume <id> alike, and a projects trust entry does not change that. Two kinds of run have such a cwd: a multi-repo run, which works at the workspace root, and a repo-less run, which works in the scratch directory from ScratchWorkspaceHandle. So every multi-repo and every repo-less Codex run failed at start. CodexHarness.BuildInvocation (backend/src/CodeSpace.Core/Services/Agents/Harnesses/Codex/CodexHarness.cs) now passes --skip-git-repo-check on both seeds. At a git cwd the flag changes nothing the model is sent.
  • At a multi-repo root, Codex's own workspace-write sandbox kept .git, .codex and .agents read-only only at the top of each writable root. That sandbox is the only boundary where our runner does not confine. So each repository's .git/hooks and .git/config were writable to the agent, and the platform's own commit and push run git there with the run's credential. Each repository below the cwd is now passed as -c sandbox_workspace_write.writable_roots=[...], which gives it the same carve-outs a single-repo cwd gets. No write access is added, and the single-repo argv is unchanged. Under our confinement Codex's sandbox is stood down and ignores the table.
  • E2E against the pinned CLIs:
    • The multi-repo arm also plants hostile config at the workspace root, the only place Codex reads project config from in that layout, so it now fails when the distrust is dropped. It is renamed …_and_loads_no_config_from_it.
    • New repo-less arm in a scratch directory.
    • The resume stand-down arm now runs at a non-git multi-repo root.
    • New unconfined-lane arm (UnconfinedWorkerE2ETests): the agent tries to append to each repository's .git/hooks/pre-push, .git/config and .codex/config.toml.
    • Gates in .github/workflows/sandbox-isolation.yml: root floor 92, unconfined lane 4, and the new markers are required.

Not in this change: hardening the platform's own git commands against repo-local hooks and config. Under confinement that exposure already exists and is pinned by A_standard_codex_writes_its_workspace_under_our_confinement_but_not_the_system_root. Loading sub-repository instructions at a multi-repo root is also left out.

Test plan

  • Unit: CodexHarnessTests 147/147 (argv for fresh and resumed multi-repo runs; no override for single-repo, scratch, read-only, a repository outside the cwd, or a prefix-only sibling; TOML quoting). Full unit suite: 11701 passed, 0 failed
  • Integration (Codex / multi-repo / workspace / harness filter): 223 passed, 2 real-model skips
  • Sandbox E2E on macOS against Codex 0.142.2 and Claude 2.1.263: 30/30 in the touched classes; whole sandbox suite 0 failures
  • Mutations against the real CLI: dropping the writable roots, the distrust, the resume seed's flag, or the flag on both seeds each makes the arm that guards it fail
  • CI sandbox-isolation: the unconfined-lane arm is the first run of Codex's own sandbox on Linux (uid 1654); the carve-out has only been observed on macOS

Two kinds of run give Codex a cwd that is no git repository. A
multi-repo run works at the workspace root, which holds each
repository in a folder of its own beside a WORKSPACE.md. A repo-less
run works in the scratch directory the executor makes under the temp
path. Codex 0.142.2 refuses such a cwd unless it is given
--skip-git-repo-check, for exec and for exec resume <id>. It exits 1
before any model request with "Not inside a trusted directory and
--skip-git-repo-check was not specified", and a projects trust entry
does not lift the refusal. CodeSpace never passed the flag, so every
multi-repo and every repo-less Codex run failed at start. The
repository-config E2E ran Codex in a single repository only, and the
real-model Codex E2Es git-init a workspace of their own, so nothing
caught it.

Every Codex invocation now carries the flag right after --json, in
both the exec seed and the exec resume seed. CodeSpace decides which
directory a run works in; Codex's git heuristic should not veto it.
Against the real CLI at a git cwd the flag changes nothing
observable. The repository's AGENTS.md and both skill roots still
load, and the untrusted marking still keeps its .codex/config.toml
and hooks out.

Starting at a multi-repo root exposed a gap in Codex's own
workspace-write sandbox, the only boundary its commands meet where
our runner does not confine. That sandbox keeps .git, .codex and
.agents read-only only at the top of each writable root, so each
repository's .git/hooks and .git/config were writable to the agent,
and the platform's own commit and push run git there with the run's
credential. Each repository below the cwd is now also named as a
writable root of its own (sandbox_workspace_write.writable_roots).
That adds no write access, since each is already inside the cwd.
The real CLI then refuses those writes, on exec and on resume, while
the agent can still change the repositories' files (observed under
macOS's sandbox). A single-repo run's argv is unchanged.

The real-CLI E2E now covers each change, and each new check goes red
under the mutation it guards:
- the multi-repo arm also plants hostile config at the workspace
  root, the one place Codex reads project config from in that
  layout, so it fails when the distrust is dropped. It is renamed to
  say it loads no config from that root;
- a repo-less arm starts Codex in a scratch directory, and fails
  without the flag;
- the resume stand-down arm runs at a non-git multi-repo root, so it
  fails without the resume seed's flag and shows the pinned CLI
  accepts the writable roots on resume;
- a new unconfined-lane arm has the agent append to each
  repository's .git/hooks/pre-push, .git/config and
  .codex/config.toml, and fails without the writable roots.
The sandbox lane's root floor moves to 92 and the unconfined lane's
to 4, each requiring the new arm's marker.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant