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

[native-assets] lock readback mismatch

Error message

[native-assets] lock readback mismatch

What it means

After writing the lock record, reading it back yielded fewer bytes or different content than written. The readback equality check ensures the lock file on disk exactly matches what this process wrote; mismatch means concurrent interference or a broken filesystem.

Source

Thrown at src/cli/native-assets.ts:578

async function acquireCacheLock(binaryPath: string, env: NodeJS.ProcessEnv): Promise<PublicationLock> {

  const path = lockPath(binaryPath);
  const started = performance.now();
  const deadline = started + lockWaitMs(env);

  for (;;) {
    const token = uuid();
    const record = lockRecord(token, binaryPath);

    const recordBytes = Buffer.from(record, 'utf8');
    try {
      const handle = await open(path, constants.O_RDWR | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
      try {
        const { bytesWritten } = await handle.write(recordBytes, 0, recordBytes.length, 0);
        if (bytesWritten !== recordBytes.length) throw new Error('[native-assets] incomplete lock write');
        const readback = Buffer.alloc(recordBytes.length);
        const { bytesRead } = await handle.read(readback, 0, readback.length, 0);
        if (bytesRead !== readback.length || !readback.equals(recordBytes)) throw new Error('[native-assets] lock readback mismatch');
        const identity = await handle.stat();
        const fileIdentity = { dev: identity.dev, ino: identity.ino, size: identity.size };
        if (!identity.isFile() || identity.nlink !== 1) throw new Error('[native-assets] unsafe publication lock');
        await reInspectPath(path, fileIdentity, true);
        return { path, token, record, identity: fileIdentity };

      } finally { await handle.close(); }
    } catch (error) {
      if (errno(error) !== 'EEXIST') throw error;
      if (performance.now() >= deadline) {
        const diagnostic = await inspectLock(path, binaryPath) ?? { path, classification: 'metadata-unavailable' as const };
        const owner = diagnostic.owner ? ` owner=${JSON.stringify(diagnostic.owner)}` : '';
        throw new Error(`[native-assets] publication-lock-timeout: ${path}; elapsed=${Math.round(performance.now() - started)}ms deadline=${lockWaitMs(env)}ms; ${diagnostic.classification}${owner}. Confirm no OMX hydration process is active for this cache key, remove only this named lock manually, then retry.`);
      }

      await new Promise<void>((done) => setTimeout(done, LOCK_RETRY_MS));
    }
  }

View on GitHub (pinned to 3ad79a8a6f)

Solutions

  1. Ensure only one hydration process runs per cache key; the timeout error message includes guidance on removing stale locks.
  2. Move the cache off synced/network filesystems.
  3. Retry hydration after the race clears.
Defensive patterns

Strategy: retry

Try / catch

try { await hydrateNativeBinary(); } catch (e) { if (/lock readback mismatch/.test(String(e))) { await sleep(1000); return hydrateNativeBinary(); } throw e; }

Prevention

When it happens

Trigger: acquireCacheLock when the immediate read of the just-written lock file differs from the record — another process overwrote the lock, or the storage layer is inconsistent (NFS caching, container overlayfs races).

Common situations: Two hydration processes racing on the same cache key; network/overlay filesystems with weak coherence; antivirus or sync agents (Dropbox/OneDrive) rewriting files in the cache.

Related errors


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