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

[native-assets] unsafe publication lock

Error message

[native-assets] unsafe publication lock

What it means

The open lock file failed the safety stat: it is not a regular file or has a hard link count other than 1. Hard-linked or non-regular lock files are treated as unsafe because they enable lock-spoofing and aliasing in the multi-process cache protocol.

Source

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

  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));
    }
  }
}

async function releaseCacheLock(lock: PublicationLock): Promise<ManagedNativeBinaryInspection | undefined> {

View on GitHub (pinned to 3ad79a8a6f)

Solutions

  1. Delete the offending lock file named in the acquire path and retry.
  2. Use a per-user cache root instead of a shared one.
  3. Audit what is hard-linking or creating non-regular files inside the cache.
Defensive patterns

Strategy: try-catch

Try / catch

try { await hydrateNativeBinary(); } catch (e) { if (/unsafe publication lock/.test(String(e))) { /* remove the offending lock file, audit for hard-link tools, retry */ } throw e; }

Prevention

When it happens

Trigger: acquireCacheLock when fstat on the newly created lock shows nlink !== 1 (someone hard-linked the lock) or the entry is not a regular file (e.g. named pipe or device).

Common situations: Multi-user caches with hard-link based dedup tools; caches manipulated by backup tools that hard-link; hostile shared /tmp-style directories.

Related errors


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