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

  1. Convert the path to an absolute canonical form with path.resolve(workspaceRoot, relativePath) before the upload call.
  2. Use the exact absolute path returned by the authorization step that admitted the file.
  3. Reject/normalize leading "./" or env-relative segments on the agent side before calling the runner tool.
  4. 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

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


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/94174a915a98e14b. Report an issue: GitHub.