Yeachan-Heo/oh-my-codex · error · Error
[native-assets] cache descendant is unsafe: ${current}
Error message
[native-assets] cache descendant is unsafe: ${current} What it means
While walking each path segment under the cache root, an intermediate (non-leaf) entry exists but is a symlink or not a directory. The hydration pipeline refuses to traverse through symlinks or files to reach the destination, as a hardening measure against symlink-swap attacks on multi-user caches.
Source
Thrown at src/cli/native-assets.ts:393
if (!create && absent(error)) return undefined;
throw error;
}
}
async function validateDescendant(path: string, canonicalRoot: string, createParents: boolean): Promise<void> {
const candidate = resolve(path);
const rel = relative(canonicalRoot, candidate);
if (!rel || rel === '..' || rel.startsWith(`..${sep}`) || resolve(canonicalRoot, rel) !== candidate) {
throw new Error('[native-assets] cache path escapes configured root');
}
const parts = rel.split(sep).filter(Boolean);
let current = canonicalRoot;
for (const [index, part] of parts.entries()) {
current = join(current, part);
const isLeaf = index === parts.length - 1;
try {
const entry = await lstat(current);
if (!isLeaf && (entry.isSymbolicLink() || !entry.isDirectory())) throw new Error(`[native-assets] cache descendant is unsafe: ${current}`);
} catch (error) {
if (!absent(error)) throw error;
if (!createParents || isLeaf) continue;
await nativeAssetsTestHooks?.beforeCreateParent?.(current);
try {
await mkdir(current, { mode: 0o700 });
} catch (error) {
if (errno(error) !== 'EEXIST') throw error;
}
const created = await lstat(current);
if (!created.isDirectory() || created.isSymbolicLink()) throw new Error(`[native-assets] cache descendant is unsafe: ${current}`);
}
}
try {
const parent = await realpath(dirname(path));
if (parent !== canonicalRoot && !parent.startsWith(`${canonicalRoot}${sep}`)) throw new Error('[native-assets] cache path escapes configured root');
} catch (error) {
if (!absent(error)) throw error;View on GitHub (pinned to 3ad79a8a6f)
Solutions
- Delete the offending entry named in the message and let hydration recreate a real directory.
- Avoid symlinking inside the native cache; point the whole cache root env var at the desired location instead.
- Clear the cache (rm -rf of the configured root) as a blunt fix.
Example fix
# before: ~/.cache/omx-native/bin -> /shared/omx-bin rm ~/.cache/omx-native/bin # after: real directory recreated by hydration; move sharing to the root via env override
Defensive patterns
Strategy: validation
Validate before calling
import { lstatSync } from 'node:fs';
import { join } from 'node:path';
function cacheSegmentsAreDirs(root: string, rel: string): boolean {
let cur = root;
for (const part of rel.split(/[\\/]/).filter(Boolean).slice(0, -1)) {
cur = join(cur, part);
try { const st = lstatSync(cur); if (!st.isDirectory() || st.isSymbolicLink()) return false; } catch { /* absent ok */ }
}
return true;
} Try / catch
try { await hydrateNativeBinary(); } catch (e) { if (/cache descendant is unsafe/.test(String(e))) { /* remove the printed symlink/file, retry */ } throw e; } Prevention
- Never symlink inside the native cache
- Relocate the entire cache via its env override instead
- Clear the cache when in doubt — it rehydrates
When it happens
Trigger: validateDescendant with createParents where some intermediate component of the destination path is a symbolic link or a regular file, e.g. cacheDir/bin being a symlink to another location.
Common situations: Users symlinking cache subdirectories to shared storage; partial/corrupt cache left by a killed process where a directory became a file; dotfile managers placing links inside the cache.
Related errors
- [native-assets] cache path escapes configured root
- [native-assets] unsafe publication lock
- [native-assets] unable to create cache root
- [native-assets] cache publication verification failed: ${fin
- [native-assets] cache publication verification failed for ${
AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27).
Data as JSON: /api/errors/898aab81bb2ed8f0.
Report an issue: GitHub.