Header image for Letting Codex Agents Commit: Making .git Writable in the workspace-write Sandbox

Letting Codex Agents Commit: Making .git Writable in the workspace-write Sandbox

Codex CLI's workspace-write sandbox lets the agent edit anything in your repo -- except commit. Every git commit dies with:

fatal: Unable to create '/path/to/repo/.git/index.lock': Read-only file system

This is deliberate. On Linux, Codex's bubblewrap sandbox mounts your writable roots read-write, then re-applies .git (plus .codex and .agents) as read-only on top. The security rationale is sound: a writable .git/hooks would let the agent plant a hook that executes outside the sandbox the next time you run git yourself.

But if you're already running with --ask-for-approval never -- YOLO mode -- you've made that trust decision, and an agent that can't commit its own work is a constant irritation. There's no official toggle: no allow_git_writes option exists anywhere in the codebase, and the open issues asking for one (#15505, #12280, #14338) have no maintainer response.

The trick: name the .git path itself

The obvious fixes don't work. Adding the repo root to sandbox_workspace_write.writable_roots fails because the .git protection is applied per writable root -- a writable parent still gets a read-only .git carved out of it (#15505). Passing extra directories with --add-dir fails because the read-only mount is applied after the command-line write dirs, so it wins (#14338).

But reading the sandbox policy source (codex-rs/protocol/src/permissions.rs), the carveout is added by a function named append_default_read_only_path_if_no_explicit_rule. The name says it all: if an explicit rule already covers the exact .git path, the read-only protection is skipped.

So point writable_roots at the .git directory itself:

codex exec --sandbox workspace-write \
  -c 'sandbox_workspace_write.writable_roots=["/path/to/repo/.git"]' \
  "commit your work"

Verified on codex-cli 0.144.6: without the flag, the commit fails with the read-only error above; with it, the commit lands -- confirmed in git log, not just the agent's self-report.

Wiring it into a shell function

The writable path is per-repo, so compute it at invocation time. My yolo-codex wrapper now does:

# Make .git writable inside the workspace-write sandbox so the agent can
# commit. Codex mounts .git read-only unless an explicit writable_roots
# entry names the .git path itself. Includes the common gitdir so linked
# worktrees can commit too.
local -a git_writable=()
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
  local git_dir git_common_dir
  git_dir=$(git rev-parse --absolute-git-dir)
  git_common_dir=$(git rev-parse --path-format=absolute --git-common-dir)
  if [[ "$git_dir" == "$git_common_dir" ]]; then
    git_writable=(-c "sandbox_workspace_write.writable_roots=[\"$git_dir\"]")
  else
    git_writable=(-c "sandbox_workspace_write.writable_roots=[\"$git_dir\",\"$git_common_dir\"]")
  fi
fi

codex --ask-for-approval never --sandbox workspace-write "${git_writable[@]}" "$@"

Two details worth the extra lines:

  • Linked worktrees. In a git worktree checkout, --absolute-git-dir points at the per-worktree gitdir, but commits also write objects and refs into the common gitdir back in the main repo. Both paths need to be writable. Tested: a commit from inside a linked worktree works with both entries, and a plain repo needs only the one.
  • The -c flag sets only writable_roots. Anything else you have under [sandbox_workspace_write] in ~/.codex/config.toml (like network_access = true) is untouched.

Outside a git repo, the array stays empty and the function behaves exactly as before.

Caveats

  • This is undocumented behavior. The explicit-rule exemption is an implementation detail of the sandbox policy, not a documented feature. A future Codex release could re-protect .git unconditionally -- if agent commits start failing after an upgrade, this is the first thing to re-check.
  • You're removing a real protection, not working around a bug. A writable .git means a writable .git/hooks, and hooks run unsandboxed when you next invoke git. If you're not already running approval-free, think twice.
  • The blunt alternative is --sandbox danger-full-access, which trades the entire filesystem sandbox to fix one directory. Carving out .git keeps everything else protected.

Tested on codex-cli 0.144.6, Linux (bubblewrap sandbox).