Yeachan-Heo/oh-my-codex · error · Error
Refusing to use unsafe created backup ancestor ${currentPath
Error message
Refusing to use unsafe created backup ancestor ${currentPath}. What it means
When an ancestor directory of the backup path does not exist, setup mkdirs it and then lstats the freshly created directory. If the created entry is a symlink or not a directory (possible via races or pre-existing entries appearing between checks), the write is aborted.
Source
Thrown at src/cli/setup.ts:1989
relativeParent === ".." ||
relativeParent.startsWith(`..${sep}`)
) {
throw new Error(`Refusing to back up ${artifact.path} outside controlled backup root.`);
}
let currentPath = backupContext.baseRoot;
for (const component of relativeParent.split(sep).filter(Boolean)) {
currentPath = join(currentPath, component);
try {
const currentStat = await lstat(currentPath);
if (currentStat.isSymbolicLink() || !currentStat.isDirectory()) {
throw new Error(`Refusing to use unsafe backup ancestor ${currentPath}.`);
}
} catch (error) {
if (!isMissingPathError(error)) throw error;
await mkdir(currentPath);
const createdStat = await lstat(currentPath);
if (createdStat.isSymbolicLink() || !createdStat.isDirectory()) {
throw new Error(`Refusing to use unsafe created backup ancestor ${currentPath}.`);
}
}
}
const handle = await open(backupPath, "wx", 0o600);
try {
await handle.writeFile(bytes);
recordRegularFileSyncOutcome(tracker, await syncNativeHookRegularFile(handle));
} finally {
await handle.close();
}
const backupStat = await lstat(backupPath);
if (backupStat.isSymbolicLink() || !backupStat.isFile() || backupStat.nlink !== 1) {
throw new Error(`Refusing unsafe native hook transaction backup ${backupPath}.`);
}
const writtenBytes = await readFile(backupPath);
if (!writtenBytes.equals(bytes)) {
throw new Error(`Native hook transaction backup verification failed for ${backupPath}.`);
}View on GitHub (pinned to 3ad79a8a6f)
Solutions
- Ensure no other process mutates the backup root during setup
- Remove the offending entry at currentPath and retry
- Serialize setup runs with a lock
- Run setup with exclusive access to the backup tree (single-user, single-process)
Defensive patterns
Strategy: retry
Validate before calling
import { lstat } from "node:fs/promises";
const st = await lstat(currentPath);
if (st.isSymbolicLink() || !st.isDirectory()) throw new Error(`unsafe created ancestor: ${currentPath}`); Try / catch
try { await createBackup(artifact); } catch (e) { if (e instanceof Error && e.message.includes("unsafe created backup ancestor")) { await rm(currentPath); await retry(createBackup, artifact); } else throw e; } Prevention
- Serialize setup runs to avoid creation races
- Remove pre-existing junk entries before running
- Use a dedicated, exclusively-owned backup root
When it happens
Trigger: mkdir(currentPath) succeeds but a concurrent process swaps the new directory for a symlink before lstat, or the entry already exists as a non-directory on platforms where mkdir is not exclusive.
Common situations: Race conditions with parallel setup runs or symlink-planting scenarios; rarely hit outside adversarial or heavily concurrent environments.
Related errors
- Refusing to use unsafe backup ancestor ${currentPath}.
- Refusing unsafe native hook transaction backup ${backupPath}
- run directory escapes the authorized runs root
- state directory escapes the authorized run directory
- session directory escapes the authorized state directory
AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27).
Data as JSON: /api/errors/8d02f44addcd5ad9.
Report an issue: GitHub.