Sandbox writable_roots bind mounts break hardlinks across roots (uv cache to .venv)

Open 💬 0 comments Opened May 27, 2026 by escape0707

What version of Codex CLI is running?

codex-cli 0.133.0

What subscription do you have?

Plus

Which model were you using?

gpt-5.5

What platform is your computer?

Linux 6.6.114.1-microsoft-standard-WSL2 x86_64

What terminal emulator and version are you using (if applicable)?

Windows Terminal, WSL2

Codex doctor report

Omitted/redacted because the full report includes local machine paths. Relevant parts:

{
  "codexVersion": "0.133.0",
  "checks": {
    "sandbox.helpers": {
      "status": "ok",
      "details": {
        "approval policy": "OnRequest",
        "filesystem sandbox": "restricted",
        "network sandbox": "enabled"
      }
    },
    "terminal.env": {
      "details": {
        "terminal": "Windows Terminal",
        "WSL_DISTRO_NAME": "present"
      }
    }
  }
}

What issue are you seeing?

When Codex runs with workspace-write and multiple writable roots, each writable root appears to be exposed as a separate bind mount. This makes hardlinks fail across writable roots even when both roots are backed by the same ext4 device.

The concrete symptom was uv warning while running a Python tool:

warning: Failed to hardlink files; falling back to full copy. This may lead to degraded performance.
         If the cache and target directories are on different filesystems, hardlinking may not be supported.

In this setup:

  • uv cache root is one writable root: <home>/.cache/uv
  • project/worktree is another writable root: <repo>
  • virtualenv is inside the project root: <repo>/api/.venv

Inside the Codex sandbox, df shows both paths on the same ext4 device, but findmnt shows them as separate bind mounts:

TARGET              SOURCE
<home>/.cache/uv    /dev/sdd[<home>/.cache/uv]
<repo>              /dev/sdd[<repo>]

Attempting a normal hardlink from the uv cache root into the project root fails:

ln: failed to create hard link '<repo>/api/.venv/codex-hardlink-dest.tmp' => '<home>/.cache/uv/codex-hardlink-source.tmp': Invalid cross-device link

But hardlinking within the project writable root succeeds, including into the same .venv:

<same inode> 2 <repo>/codex-hardlink-source.tmp
<same inode> 2 <repo>/api/.venv/codex-hardlink-dest.tmp

This looks related to #8636 and #10864, but those are Cargo/Rust cases. This report is specifically about hardlinks across multiple Codex writable roots, reproduced with plain ln and surfaced by uv.

What steps can reproduce the bug?

Use a workspace-write Codex session with both of these as writable roots:

<home>/.cache/uv
<repo>

Then run:

uv cache dir
readlink --canonicalize api/.venv
df --print-type <home>/.cache/uv api/.venv
findmnt --target <home>/.cache/uv --output TARGET,SOURCE,FSTYPE,OPTIONS
findmnt --target "$PWD/api/.venv" --output TARGET,SOURCE,FSTYPE,OPTIONS

Then test cross-root hardlinking:

src=<home>/.cache/uv/codex-hardlink-source.tmp
dst="$PWD/api/.venv/codex-hardlink-dest.tmp"
rm --force "$src" "$dst"
printf 'hardlink-test\n' > "$src"
ln "$src" "$dst"

Observed:

Invalid cross-device link

Control test inside the project writable root:

src="$PWD/codex-hardlink-source.tmp"
dst="$PWD/api/.venv/codex-hardlink-dest.tmp"
rm --force "$src" "$dst"
printf 'hardlink-test\n' > "$src"
ln "$src" "$dst"
stat --format '%i %h %n' "$src" "$dst"

Observed: succeeds with the same inode and link count 2.

What is the expected behavior?

If two writable roots are backed by the same underlying filesystem, hardlinks between them should either work, or Codex should document that each writable root is isolated as a separate mount and cross-root hardlinks are expected to fail.

This matters for package managers and build tools that optimize by hardlinking from caches into project-local environments, such as uv cache to .venv.

Additional information

Workarounds:

  • UV_LINK_MODE=copy suppresses the uv warning and uses copies instead of hardlinks.
  • Moving UV_CACHE_DIR under the project writable root should also avoid the warning, but creates project-local cache clutter.

Related issues:

  • #8636
  • #10864

View original on GitHub ↗