Yeachan-Heo/oh-my-codex · error · Error
Refusing unsafe native hook transaction backup ${backupPath}
Error message
Refusing unsafe native hook transaction backup ${backupPath}. What it means
After opening the backup file exclusively ('wx', mode 0600), writing, fsyncing, and closing, setup lstats the final path. If it is a symlink, not a regular file, or has a hardlink count other than 1, the backup is considered unsafe and rejected — the backup must be a sole, regular, non-symlink file.
Source
Thrown at src/cli/setup.ts:2002
} 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}.`);
}
}
if (options.verbose) {
console.log(` backup ${artifact.path} -> ${backupPath}`);
}
return true;
}
async function commitNativeHookTransaction(
artifacts: readonly NativeHookTransactionArtifact[],
preconditions: readonly NativeHookTransactionPrecondition[],
ancestorPrecondition: NativeHookTransactionAncestorPrecondition,
backupContext: SetupBackupContext,
tracker: RegularFileDurabilityTracker,View on GitHub (pinned to 3ad79a8a6f)
Solutions
- Verify no hardlinks: check nlink of the backup file and remove external links
- Ensure the backup directory is not symlinked and not on exotic filesystems
- Re-run setup once the tree is clean
- Audit for processes that link or replace files under the backup root
Defensive patterns
Strategy: validation
Validate before calling
import { lstat } from "node:fs/promises";
const st = await lstat(backupPath).catch(() => null);
if (st && (st.isSymbolicLink() || !st.isFile() || st.nlink !== 1)) throw new Error(`unsafe backup: ${backupPath}`); Try / catch
try { await createBackup(artifact); } catch (e) { if (e instanceof Error && e.message.includes("Refusing unsafe native hook transaction backup")) { /* remove hardlinks/symlinks at backupPath, retry */ } else throw e; } Prevention
- Avoid hardlinking backup files
- Disable filesystem dedup features on the backup volume
- Monitor nlink on sensitive config backups
When it happens
Trigger: The written backupPath resolves to a symlink, a non-regular file, or is hardlinked elsewhere (nlink > 1) by the time of the post-write lstat.
Common situations: Hardlink attacks or accidents (backup dir on a filesystem with dedup), symlink planting races, or test injection substituting the file.
Related errors
- Refusing to use unsafe backup ancestor ${currentPath}.
- Refusing to use unsafe created backup ancestor ${currentPath
- 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/d820db9b3ca503e2.
Report an issue: GitHub.