Yeachan-Heo/oh-my-codex · error · Error
Native hook transaction rollback preserved ${artifact.path}
Error message
Native hook transaction rollback preserved ${artifact.path} for manual recovery because ${artifact.label} no longer matches the transaction-owned version. What it means
During rollback of a native hook transaction, setup re-captures the artifact on disk and compares it to the snapshot taken when the change was applied. If they differ, the file was modified by something else after the transaction applied it, so rollback refuses to overwrite it and preserves it for manual recovery.
Source
Thrown at src/cli/setup.ts:1793
if (!validation.ok) {
throw new Error(
`Native hook transaction wrote invalid hooks.json: ${validation.error.message}`,
);
}
}
if (artifact.kind === "config") TOML.parse(content);
}
async function restoreNativeHookTransactionArtifact(
applied: AppliedNativeHookTransactionArtifact,
ancestorPrecondition: NativeHookTransactionAncestorPrecondition,
tracker: RegularFileDurabilityTracker,
assertRollbackState: () => Promise<void>,
): Promise<void> {
const { artifact } = applied;
const current = await captureNativeHookTransactionArtifact(artifact.path, artifact.label);
if (!nativeHookTransactionSnapshotsEqual(current, applied.appliedSnapshot)) {
throw new Error(
`Native hook transaction rollback preserved ${artifact.path} for manual recovery because ${artifact.label} no longer matches the transaction-owned version.`,
);
}
if (artifact.before.bytes === null) {
injectNativeHookTransactionFailure("before_rollback", artifact);
injectNativeHookTransactionFailure("before_rollback_remove", artifact);
await assertNativeHookTransactionArtifactSnapshot(
artifact.path,
artifact.label,
applied.appliedSnapshot,
"rollback",
);
await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
await assertRollbackState();
injectNativeHookTransactionFailure("after_final_restore_validation", artifact);
const claimPath = nativeHookTransactionClaimPath(artifact.path);
await rename(artifact.path, claimPath);View on GitHub (pinned to 3ad79a8a6f)
Solutions
- Diff the preserved file against the expected transaction-owned version and reconcile edits manually
- Ensure only one setup/transaction process runs at a time (lock file)
- Re-run setup after manual reconciliation so it starts from a known state
- Remove test fault-injection points that mutate files mid-transaction
Defensive patterns
Strategy: try-catch
Validate before calling
const snapshot = await captureNativeHookTransactionArtifact(path, label);
if (!nativeHookTransactionSnapshotsEqual(snapshot, expected)) {
console.warn(`${path} changed externally; aborting before apply`);
} Try / catch
try { await tx.rollback(); } catch (e) { if (e instanceof Error && e.message.includes("no longer matches the transaction-owned version")) { /* diff preserved file vs expected, reconcile manually */ } else throw e; } Prevention
- Run only one setup/transaction at a time
- Use a lock file around hook-mutating operations
- Treat mid-transaction edits as conflicts: reconcile then re-run
When it happens
Trigger: Rolling back a transaction after another process (editor, second setup run, user edit) modified hooks.json or config.toml between apply and rollback.
Common situations: Concurrent setup invocations, a hook-editing tool or editor autosave racing the transaction, or delayed fault-injection in tests.
Related errors
- Native hook transaction rollback preserved ${artifact.path}
- Native hook transaction rollback removal claim failed (${err
- Native hook transaction failed (${message}) and rollback fai
- Native hook transaction failed and was rolled back: ${messag
- autoresearch_active_run_exists
AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27).
Data as JSON: /api/errors/ff6693cc5d633f20.
Report an issue: GitHub.