Linux sandbox leaks empty .git/.agents/.codex dirs into /tmp when the helper is killed, and later runs never reclaim them
What version of Codex CLI is running?
codex-cli 0.143.0
What subscription do you have?
ChatGPT Pro (OAuth)
Which model were you using?
gpt-5.5
What platform is your computer?
Ubuntu 24.04, x86_64, kernel 6.17
What terminal emulator and version are you using (if applicable)?
Not applicable, codex exec invoked non-interactively by an agent harness.
Codex doctor report
Omitted intentionally. This is not an install, config, or auth issue; the behavior is root-caused in source below and reproduces on a healthy install.
What issue are you seeing?
Empty .git, .agents, and .codex directories appear at the top of /tmp and never go away. A stray empty /tmp/.git breaks Go builds in any git worktree under /tmp: with -buildvcs=auto, cmd/go walks up past the worktree's .git file, finds /tmp/.git, treats /tmp as the repository root, and fails with error obtaining VCS status: exit status 128. It also confuses git repository discovery for anything else running under /tmp.
Because the directories only leak when a sandboxed run dies, they reappear at unpredictable times (in my case 00:03 and 04:02 on the same night). It took an evening of forensics to trace them back to Codex; until then every scheduled job on the machine was a suspect.
Root cause, from reading codex-rs/linux-sandbox:
- Under
workspace-write,/tmpis a writable root (exclude_slash_tmpdefaults to false). For each writable root, missing protected metadata paths (.git,.agents,.codex) are masked with--perms 555 --tmpfs <path> --remount-ro <path>(append_missing_read_only_subpath_argsinbwrap.rs). Since the writable root is bind-mounted from the host, creating the mount point materializes a real empty directory on the host, e.g./tmp/.git. - These are registered as synthetic mount targets and removed during cleanup (
cleanup_synthetic_mount_targetsinlinux_run_main.rs). If the helper dies before cleanup (SIGKILL, OOM kill, crash), the directories persist on the host and the marker file in the registry ($TMPDIR/codex-bwrap-synthetic-mount-targets-<uid>/<hash>/<pid>) goes stale. - The leak is then permanent. On the next run over the same path,
register_synthetic_mount_targetsonly adopts an existing path as synthetic when the registry shows an active owner (synthetic_mount_marker_dir_has_active_synthetic_owner). The stale marker from the dead process, which is the only evidence the path is a leaked mount point rather than a real pre-existing directory, is deleted during the scan without being acted on. Every subsequent run then preserves the leaked directory as pre-existing, forever.
What steps can reproduce the bug?
- In a directory under
/tmp(a git worktree matches the real-world case), start a run:codex exec --skip-git-repo-check -s workspace-write "run: sleep 60". - While the command is running, confirm
/tmp/.git,/tmp/.agents,/tmp/.codexexist as read-only tmpfs mounts inside the sandbox and as directories on the host. kill -9the sandbox helper process mid-command.- Observe the three empty directories left behind in
/tmp. - Run any later
codex exec -s workspace-writecommand and observe they are never cleaned up. - In any git worktree under
/tmp, rungo build ./...on a module and observeerror obtaining VCS status: exit status 128.
On a clean exit the directories are removed correctly; only a killed helper leaks them, which is what makes the recurrence look random.
What is the expected behavior?
Sandbox runs should not leave permanent artifacts in /tmp (or any writable root). At minimum, a later run that encounters a leaked synthetic mount point should reclaim it instead of promoting it to a pre-existing path.
Additional information
I have a tested fix and am happy to open a PR if you want it, per the invitation flow in docs/contributing.md. Outline: during registration, count stale synthetic markers (dead pid, synthetic contents) as ownership evidence, so the next run over the same path adopts the leaked mount point and removes it in its own cleanup. Markers recorded as existing still preserve the path, and cleanup still refuses to remove non-empty directories and non-empty files, so a real repository, a worktree .git file, or user data is never touched. The change is contained to linux_run_main.rs plus two tests in linux_run_main_tests.rs (leaked empty dir with a stale synthetic marker is adopted and removed; pre-existing path with a stale existing marker is preserved). cargo test -p codex-linux-sandbox passes (94 tests), fmt and clippy clean.
Related, possibly the same family: #31225 reports the Windows Desktop app creating an empty .git directory, and #18918 covers the Windows ACL flavor of metadata protection in writable roots.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗