multica-ai/multica · critical
codex home %s was replaced while opening it; refusing to wri
Error message
codex home %s was replaced while opening it; refusing to write %s through it
What it means
os.SameFile compares the opened os.Root handle with the current Lstat result and they differ: the directory at codexHome was swapped (rename) between open and stat. The daemon refuses to write through the stale handle's identity because the path now names something else — same anti-symlink defense extended to atomic directory replacement.
Source
Thrown at server/internal/daemon/execenv/codex_home.go:994
// verifyCodexHomeRoot proves that root is the directory codexHome names right
// now: not reached through a symlink, and the same directory os.Lstat sees at
// that path. It is separate from openVerifiedCodexHomeRoot so the swap case can
// be tested deterministically instead of by racing.
func verifyCodexHomeRoot(root *os.Root, codexHome, key string) error {
opened, err := root.Stat(".")
if err != nil {
return fmt.Errorf("stat opened codex home %s: %w", codexHome, err)
}
current, err := os.Lstat(codexHome)
if err != nil {
return fmt.Errorf("stat codex home %s: %w", codexHome, err)
}
if current.Mode()&os.ModeSymlink != 0 {
return fmt.Errorf("codex home %s is a symlink; refusing to write %s through it", codexHome, key)
}
if !os.SameFile(opened, current) {
return fmt.Errorf("codex home %s was replaced while opening it; refusing to write %s through it", codexHome, key)
}
return nil
}
// materialiseInCodexHome writes src to relPath inside codexHome using
// root-scoped operations, so no symlink below the task home can redirect the
// daemon's mkdir, remove, or write outside it.
//
// This matters because a task home is reused: a prepare can run against a
// directory a previous task already wrote to. Without the root, a task that
// replaced an intermediate directory of its own home with a link to somewhere
// else would have the daemon delete and overwrite the link target on the next
// prepare. os.Root still allows links that stay inside the task home, which is
// harmless, and rejects the ones that leave it. The root itself is
// identity-checked by openVerifiedCodexHomeRoot.
func materialiseInCodexHome(codexHome, relPath, src, key string) error {
root, err := openVerifiedCodexHomeRoot(codexHome, key)
if err != nil {View on GitHub (pinned to 2c0912b6ec)
Solutions
- Re-run the prepare — the next open picks up the current directory
- Stop tooling that swaps workspace directories while tasks start
- Investigate task code that renames its own home
Defensive patterns
Strategy: retry
Try / catch
if err := prepareCodexHome(...); err != nil {
if strings.Contains(err.Error(), "was replaced while opening") {
err = prepareCodexHome(...) // next open sees the new directory
}
} Prevention
- Stop tooling that renames/swaps workspace dirs during prepare
- Alert on repeated occurrences — they indicate a hostile or broken task
When it happens
Trigger: A rename swapping the home directory concurrently with openVerifiedCodexHomeRoot; a task that replaces its own home via rename during a previous prepare.
Common situations: Task code atomically rotating its home; external tooling re-provisioning workspace directories mid-prepare.
Related errors
- stat codex home %s: %w
- codex home %s is a symlink; refusing to write %s through it
- create %s copy %s: %w
- fixed_args entries cannot contain NUL bytes
- command_name cannot contain NUL bytes
AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15).
Data as JSON: /api/errors/23f750ca30241583.
Report an issue: GitHub.