santifer/career-ops · error
${label} escapes the tracker workspace: ${absPath}
Error message
${label} escapes the tracker workspace: ${absPath} What it means
assertInsideWorkspace() in generate-pdf.mjs canonicalizes the deepest existing ancestor of the path with realpathSync(). If that call throws — the ancestor vanished between existsSync() and realpathSync() (a TOCTOU race), a parent directory denies read/traverse (EACCES), or an NFS/symlink quirk — containment is unprovable, and the guard fails closed rather than fall back to a lexical path a symlinked ancestor could slip past.
Source
Thrown at generate-pdf.mjs:88
* @throws {Error} when the canonical path escapes the tracker workspace.
*/
function assertInsideWorkspace(absPath, label) {
let probe = absPath;
const tail = [];
while (!existsSync(probe)) {
tail.unshift(basename(probe));
const parent = dirname(probe);
if (parent === probe) break; // reached the filesystem root
probe = parent;
}
let canonical;
try {
canonical = existsSync(probe) ? resolve(realpathSync(probe), ...tail) : absPath;
} catch {
// Canonicalization failed (realpath raced away, permission error): containment
// is unprovable, so fail closed rather than fall back to a lexical form that a
// symlinked ancestor could slip past.
throw new Error(`${label} escapes the tracker workspace: ${absPath}`);
}
const rel = relative(__workspaceRoot, canonical);
if (rel === '' || rel.startsWith('..') || isAbsolute(rel)) {
throw new Error(`${label} escapes the tracker workspace: ${absPath}`);
}
return absPath;
}
// Ensure output directory exists (fresh setup)
mkdirSync(resolve(workspaceRoot, 'output'), { recursive: true });
/**
* Normalize text for ATS compatibility by converting problematic Unicode.
*
* ATS parsers and legacy systems often fail on em-dashes, smart quotes,
* zero-width characters, and non-breaking spaces. These cause mojibake,
* parsing errors, or display issues. See issue #1.
*View on GitHub (pinned to 60398d6549)
Solutions
- Re-run once — a transient realpath race self-heals when the filesystem settles.
- If persistent, check traverse permission on every parent: `namei -l <path>` shows where access stops, and fix ownership/permissions there.
- Avoid symlinked ancestors for the workspace; use the real directory path.
Defensive patterns
Strategy: try-catch
Validate before calling
import { existsSync } from 'node:fs';
import { dirname } from 'node:path';
// cheap pre-flight: the deepest existing ancestor must be stable and readable
function ancestorsReadable(p) {
let d = p;
while (!existsSync(d)) d = dirname(d);
try { accessSync(d, constants.R_OK); return true; } catch { return false; }
} Try / catch
try {
assertInsideWorkspace(path, 'input');
} catch (err) {
if (err.message.includes('escapes the tracker workspace')) {
if (isTransientFsError) { await once(setTimeout, 250); assertInsideWorkspace(path, 'input'); } // one retry for realpath races
else { console.error(`Path containment failed for ${path} — check permissions/symlinks on its parents.`); process.exit(1); }
} else throw err;
} Prevention
- Don't run filesystem cleanup jobs that delete workspace directories while renders are in flight.
- Ensure every parent of the workspace is traversable (namei -l) by the running user.
- Avoid placing the workspace behind symlinks or on flaky network mounts.
When it happens
Trigger: A concurrent process deletes or renames a parent directory mid-render; the workspace sits under a parent dir with restrictive permissions; a symlinked ancestor whose target is on a flaky mount.
Common situations: Parallel batch renders racing a cleanup job over output/; running as a different user than the workspace owner; containerized runs where only part of the path is visible.
Related errors
- refusing to hash symlink: ${childRel}
- refusing to archive restricted destination: ${preGuard.reaso
- refusing to archive restricted destination after redirect: $
- Invalid or blocked URL: ${rejected.reason}
- Unsupported profile photo URL scheme: ${photo.split(':', 1)[
AI-assisted analysis of santifer/career-ops@60398d6549 (2026-08-20).
Data as JSON: /api/errors/068a3cece297dcb2.
Report an issue: GitHub.