paperclipai/paperclip · error
Workspace file must have a canonical absolute path
Error message
Workspace file must have a canonical absolute path
What it means
openRunnerApiWorkspaceFile opens a previously authorized workspace file for runner API uploads without following raced symlinks. It requires an absolute canonical path; a relative path fails immediately with "Workspace file must have a canonical absolute path". This is a confinement precondition — the caller must pass the exact absolute path that was authorized.
Source
Thrown at server/src/services/native-runtime/runner-api-files.ts:7
import { constants } from "node:fs";
import { open, type FileHandle } from "node:fs/promises";
import { isAbsolute } from "node:path";
/** Open a previously authorized canonical path without following raced symlinks. */
export async function openRunnerApiWorkspaceFile(path: string): Promise<FileHandle> {
if (!isAbsolute(path)) throw new Error("Workspace file must have a canonical absolute path");
if (process.platform === "darwin") {
// Darwin sys/fcntl.h: O_NOFOLLOW_ANY rejects symlinks at every component.
// Node does not expose this flag in fs.constants. Unsupported kernels fail
// closed instead of falling back to a pathname check followed by open.
return open(path, constants.O_RDONLY | constants.O_NONBLOCK | 0x20000000);
}
if (process.platform !== "linux") throw new Error("Workspace uploads require a platform with confined file opens; use an authorized artifact reference");
const parts = path.split("/").filter(Boolean);
if (!parts.length || parts.some(part => part === "." || part === "..")) throw new Error("Invalid canonical workspace path");
let directory = await open("/", constants.O_RDONLY | constants.O_DIRECTORY);
try {
for (const part of parts.slice(0, -1)) {
// Linux magic descriptor links provide openat-style directory confinement.
const next = await open(`/proc/self/fd/${directory.fd}/${part}`, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
await directory.close();
directory = next;
}
return await open(`/proc/self/fd/${directory.fd}/${parts.at(-1)}`, constants.O_RDONLY | constants.O_NONBLOCK | constants.O_NOFOLLOW);View on GitHub (pinned to 01ad858492)
Solutions
- Convert the path to an absolute canonical form with path.resolve(workspaceRoot, relativePath) before the upload call.
- Use the exact absolute path returned by the authorization step that admitted the file.
- Reject/normalize leading "./" or env-relative segments on the agent side before calling the runner tool.
- If the file truly has no workspace root, use an authorized artifact reference instead of a workspace path.
Example fix
// before
await openRunnerApiWorkspaceFile("src/report.pdf");
// after
import { resolve } from "node:path";
await openRunnerApiWorkspaceFile(resolve("/workspace", "src/report.pdf")); Defensive patterns
Strategy: validation
Validate before calling
import { isAbsolute } from "node:path";
function ensureCanonicalWorkspacePath(p: string): string {
if (!isAbsolute(p)) throw new Error(`workspace file path must be absolute, got: ${p}`);
return p;
}
// call before invoking any upload that reaches openRunnerApiWorkspaceFile Type guard
function isAbsoluteWorkspacePath(p: string): boolean { return isAbsolute(p); } Try / catch
try {
const handle = await openRunnerApiWorkspaceFile(path);
} catch (err) {
if (err instanceof Error && err.message === "Workspace file must have a canonical absolute path") {
// convert to absolute via path.resolve(workspaceRoot, path) and retry once
} else throw err;
} Prevention
- Have the tool layer normalize every agent-supplied path with path.resolve(workspaceRoot, input) before use.
- Pass forward the absolute path returned by the file-authorization step, not the agent's original string.
- Log the offending path in the error path to make relative-path bugs obvious.
When it happens
Trigger: A runner file-upload tool call supplies a workspace file path that is relative (e.g. "src/index.ts", "./file.txt") instead of an absolute canonical path like "/workspace/src/index.ts".
Common situations: Agent resolves a file against its own cwd and passes the relative form; path built by string concatenation without path.resolve; LLM tool argument omits the workspace root prefix; hand-written scripts inside the run calling the API with relative paths.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- Workspace uploads require a platform with confined file open
- Materialized OpenCode executable has unsafe permissions
- A trusted viewer build is required for public chat reports
- Trusted viewer must not use symlinks
- Unexpected trusted viewer asset
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/94174a915a98e14b.
Report an issue: GitHub.