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
- Delete the offending lock file named in the acquire path and retry.
- Use a per-user cache root instead of a shared one.
- 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
- Use per-user cache roots
- Don't run hard-link dedup/backup tools over the cache
- Remove stale locks before retrying
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
- [native-assets] cache path escapes configured root
- [native-assets] cache descendant is unsafe: ${current}
- [native-assets] incomplete lock write
- [native-assets] lock readback mismatch
- [native-assets] publication-lock-timeout: ${path}; elapsed=$
AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27).
Data as JSON: /api/errors/5ff837c5b0f1b4bd.
Report an issue: GitHub.