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 expected restored version.

What it means

After rollback writes the pre-transaction bytes back to artifact.path, setup re-reads the file and verifies it matches the recorded before-state (bytes and topology such as link count / symlink status). A mismatch means the restore did not land as expected, so the file is preserved for manual recovery instead of silently accepted.

Source

Thrown at src/cli/setup.ts:1877

			(stabilizedSnapshot) => {
				applied.appliedSnapshot = stabilizedSnapshot;
			},
			assertRollbackState,
		);
	}
	const restored = await assertNativeHookTransactionArtifactSnapshot(
		artifact.path,
		artifact.label,
		applied.appliedSnapshot,
		"rollback",
	);
	if (
		!nativeHookTransactionSnapshotMatchesExpected(restored, {
			bytes: artifact.before.bytes,
			topology: artifact.before.topology,
		})
	) {
		throw new Error(
			`Native hook transaction rollback preserved ${artifact.path} for manual recovery because ${artifact.label} no longer matches the expected restored version.`,
		);
	}
}

async function cleanupNativeHookTransactionStagedDeletions(
	applied: readonly AppliedNativeHookTransactionArtifact[],
	ancestorPrecondition: NativeHookTransactionAncestorPrecondition,
	tracker: RegularFileDurabilityTracker,
	preconditions?: readonly NativeHookTransactionPrecondition[],
	rollbackApplied?: readonly AppliedNativeHookTransactionArtifact[],
): Promise<void> {
	for (const entry of applied) {
		if (
			!entry.stagedDeletionPath ||
			!entry.stagedDeletionSnapshot ||
			entry.stagedDeletionCleaned
		) {

View on GitHub (pinned to 3ad79a8a6f)

Solutions

  1. Manually compare artifact.path with the intended before-bytes recorded in the transaction log
  2. Stop concurrent writers and re-run setup
  3. Check for filesystem-level oddities (bind mounts, symlinks pointing at the path)
  4. Verify no test injection hooks fired during restore
Defensive patterns

Strategy: try-catch

Try / catch

try { await tx.rollback(); } catch (e) { if (e instanceof Error && e.message.includes("no longer matches the expected restored version")) { /* manually restore from backup root copy, then re-run setup */ } else throw e; }

Prevention

When it happens

Trigger: Post-restore verification where the file on disk differs from artifact.before — e.g. another writer touched it immediately after restore, or topology (symlink/hardlink) diverged.

Common situations: Racing processes, platforms where rename/replace semantics differ (Windows), or fault injection at before_rollback checkpoints.

Related errors


AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27). Data as JSON: /api/errors/ef92eda3bf3e9e7c. Report an issue: GitHub.