Yeachan-Heo/oh-my-codex · critical · Error

(cancellation_rollback_failed: ).

Error message

${primaryMessage} (cancellation_rollback_failed:${rollbackFailureCount}${sample}).

What it means

While rolling back already-opened files after a write failure during cancellation, one or more rollback writes themselves failed. The original write error is rethrown, annotated with cancellation_rollback_failed:<count> plus the paths (sample) of files that could not be restored, so the user knows the state directory is now partially inconsistent.

Solutions

  1. Check disk space and filesystem health (df -h, dmesg for I/O errors) on the state directory
  2. Verify no other process holds/locks the state files (lsof, antivirus exclusions)
  3. Inspect the sampled paths in the message and manually restore them from backup/journal
  4. Retry the operation from a clean state after freeing space; validate state integrity afterwards

Example fix

// before
await runOperation(opts); // Error: write failed (cancellation_rollback_failed:2:state/a.json:state/b.json)

// after
try {
  await runOperation(opts);
} catch (e) {
  if (/cancellation_rollback_failed/.test(String(e))) await verifyAndRepairState();
  throw e;
}
Defensive patterns

Strategy: fallback

Validate before calling

import { promises as fs } from "node:fs";
await fs.access(stateDir, fs.constants.W_OK);
// crude free-space check on POSIX
const stat = await fs.statfs ? fs.statfs(stateDir) : null;
if (stat && Number(stat.bavail) * Number(stat.bsize) < 50 * 1024 * 1024) {
  throw new Error('Insufficient space for rollback safety');
}

Type guard

function isRollbackFailure(e: unknown): e is Error & { cause: unknown } {
  return e instanceof Error && /cancellation_rollback_failed/.test(e.message);
}

Try / catch

try {
  await runOperation(opts);
} catch (e) {
  if (isRollbackFailure(e)) {
    const [, count, sample] = e.message.match(/cancellation_rollback_failed:(\d+)(?::(.+))?\./) ?? [];
    await repairState(sample ? sample.split(',') : []); // restore listed paths from backup/journal
  }
  throw e;
}

Prevention

When it happens

Trigger: A write fails mid-cancellation, then during the rollback loop an ftruncate/write on an already-opened handle throws (ENOSPC, EIO, file truncated externally, permissions changed). rollbackFailureCount > 0 and the aggregate error is thrown instead of the bare writeError.

Common situations: Full disk (ENOSPC) while restoring files; NFS/network filesystem write errors; antivirus or security software locking handles on Windows; external truncation of state files between open and rollback.

Related errors


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

Appendix: source

Thrown at src/cli/index.ts:8710

        for (const committedEntry of committed.reverse()) {
          try {
            if (cancellationTestRollbackFailureMode === committedEntry.mode) {
              throw new Error(`Injected cancellation rollback failure for ${committedEntry.mode}.`);
            }
            await committedEntry.handle.truncate(0);
            await committedEntry.handle.write(committedEntry.entry.originalContent, 0, "utf-8");
            await committedEntry.handle.sync();
          } catch {
            rollbackFailureCount += 1;
            if (rollbackFailureSample.length < 3) {
              rollbackFailureSample.push(committedEntry.mode.replace(/[^A-Za-z0-9_-]/g, "_"));
            }
          }
        }
        if (rollbackFailureCount > 0) {
          const primaryMessage = writeError instanceof Error ? writeError.message : String(writeError);
          const sample = rollbackFailureSample.length > 0 ? `:${rollbackFailureSample.join(",")}` : "";
          throw new Error(`${primaryMessage} (cancellation_rollback_failed:${rollbackFailureCount}${sample}).`, { cause: writeError });
        }
        throw writeError;
      }
    } finally {
      await Promise.all(opened.map(({ handle }) => handle.close().catch(() => undefined)));
    }

    for (const mode of reported) {
      console.log(`Cancelled: ${mode}`);
    }

    if (reported.size === 0) {
      console.log("No active modes to cancel.");
    }
  } catch (err) {
    logCliOperationFailure(err);
    process.exitCode = 1;
  }

View on GitHub (pinned to 3ad79a8a6f)