santifer/career-ops · error
could not be canonicalized against the tracker workspace (…
Error message
${label} could not be canonicalized against the tracker workspace (${/** @type {any} */ (err)?.code || 'realpath failed'} on ${probe}): ${absPath} What it means
generate-pdf.mjs's assertInsideWorkspace proves a manifest input/output path stays inside the tracker workspace by resolving it through realpath. When realpath itself fails (path raced away, permission error), containment cannot be proven, so the guard fails closed with this distinct 'could not be canonicalized' error rather than silently falling back to a lexical check a symlinked ancestor could bypass. It is deliberately named differently from the 'escapes' error so CI failures are diagnosable.
Solutions
- Re-run the job — if intermittent (a race or CI sandbox hiccup), a retry usually passes
- Check permissions on every ancestor directory of the path: the user running generate-pdf must be able to traverse (x) each one
- Ensure the path exists and is not being deleted concurrently by another step in the pipeline
- If a CI sandbox causes it, exclude the workspace directory from the sandbox's realpath/permission restrictions or run the tool outside the restricted mount
- Verify the manifest paths are correct absolute-or-manifest-relative paths inside the tracker workspace
Example fix
// before (manifest entry pointing into a raced temp dir)
{"input": "/tmp/runner-tmp/cv/out.html", "output": "output/cv.pdf"}
// after (stable path inside the workspace)
{"input": "output/cv-tailored.html", "output": "output/cv-tailored.pdf"} Defensive patterns
Strategy: try-catch
Validate before calling
import { existsSync, statSync } from 'node:fs';
function isReadable(path) {
let p = path;
while (p && !existsSync(p)) p = p.slice(0, p.lastIndexOf('/')) || '/';
try { statSync(p); return true; } catch { return false; }
}
if (!isReadable(entryPath)) throw new Error(`unreachable path: ${entryPath}`); Type guard
const isProbedPath = (v) => typeof v === 'string' && v.length > 0 && require('node:path').isAbsolute(v); Try / catch
try {
assertInsideWorkspace(entryInput, 'input');
} catch (err) {
if (err.message.includes('could not be canonicalized')) {
console.error('Path could not be verified (permissions or race):', err.message);
console.error('Check ancestor-directory permissions or retry; containment cannot be proven.');
process.exit(2);
}
throw err;
} Prevention
- Ensure the process user has traverse (x) permission on every ancestor of manifest paths
- Avoid deleting/renaming input directories while a batch render is running
- In CI, run the tool in a workspace directory excluded from sandbox realpath restrictions
- Verify manifest paths exist before launching the batch
When it happens
Trigger: Calling the batch manifest path with an entry whose input path's nearest existing ancestor cannot be realpathed: the file or directory was deleted between the existsSync probe and realpathSync (TOCTOU race), or realpathSync returns EACCES/EPERM on an ancestor directory (common for sandboxed CI runners probing through restricted mount points).
Common situations: Intermittent macOS-CI-only hits (issue #3162) where a sandboxed runner denies realpath on a path that is lexically inside the workspace; parallel jobs deleting/renaming temp output directories mid-run; a manifest pointing at a path under a directory the CI user cannot traverse.
Related errors
- escapes the tracker workspace: (workspaceRoot= canonical=…
- refusing to hash non-regular file
- Refusing to write the cover letter outside output/
- a 40-hex commit --sha is required
- a16z-speedrun-talent: URL must use HTTPS
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/d1610f7b4fff3906.
Report an issue: GitHub.
Appendix: source
Thrown at generate-pdf.mjs:119
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 (err) {
// 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. Named as its own failure, not as an
// escape: an intermittent CI-only hit of this guard (#3162) was undiagnosable
// while both branches threw the same message — "escapes" points a reader at
// the path, when the actual event was realpath failing underneath it.
throw new Error(
`${label} could not be canonicalized against the tracker workspace`
+ ` (${/** @type {any} */ (err)?.code || 'realpath failed'} on ${probe}): ${absPath}`,
);
}
const workspace = canonicalWorkspaceRoot();
const rel = relative(workspace, canonical);
if (rel === '' || rel.startsWith('..') || isAbsolute(rel)) {
// #3162: an intermittent macOS-CI-only hit of this branch happens on paths
// that are lexically inside the sandbox, and canonicalization SUCCEEDS
// before it. Print both sides so the next occurrence names the disagreeing
// ancestor outright instead of asking a reader to reconstruct it.
throw new Error(
`${label} escapes the tracker workspace: ${absPath}`
+ ` (workspaceRoot=${workspace} canonical=${canonical} rel=${rel})`,
);
}
return absPath;
}View on GitHub (pinned to aac998c7ed)