paperclipai/paperclip · error

filesystemExtraPaths[${index}] must be an absolute path or {

Error message

filesystemExtraPaths[${index}] must be an absolute path or { path, access } object.

What it means

Thrown by parseLocalProcessSandboxExtraPaths when an entry in the filesystemExtraPaths array is neither a string nor a plain object. Strings are accepted (treated as { path: entry, access: "ro" }); plain objects are accepted if they have the right shape; everything else (number, boolean, null, array) is rejected.

Source

Thrown at packages/adapter-utils/src/local-process-sandbox.ts:500

    const proxyUrl = `http://127.0.0.1:${SANDBOX_PROXY_PORT}`;
    env.HTTP_PROXY = proxyUrl;
    env.HTTPS_PROXY = proxyUrl;
    env.http_proxy = proxyUrl;
    env.https_proxy = proxyUrl;
  }

  args.push("--chdir", cwd, "--", executable, ...executableArgs);
  return { command: bwrapCommand, args, cwd: "/", env, cleanup };
}

export function parseLocalProcessSandboxExtraPaths(value: unknown): LocalProcessSandboxPath[] {
  if (!Array.isArray(value)) return [];
  return value.map((entry, index) => {
    if (typeof entry === "string") {
      return { path: normalizeAbsolutePath(entry, `filesystemExtraPaths[${index}]`), access: "ro" };
    }
    if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
      throw new Error(`filesystemExtraPaths[${index}] must be an absolute path or { path, access } object.`);
    }
    const raw = entry as Record<string, unknown>;
    const access = raw.access === "rw" ? "rw" : raw.access === "ro" || raw.access == null ? "ro" : null;
    if (!access || typeof raw.path !== "string") {
      throw new Error(`filesystemExtraPaths[${index}] must use access "ro" or "rw" and an absolute path.`);
    }
    return { path: normalizeAbsolutePath(raw.path, `filesystemExtraPaths[${index}].path`), access };
  });
}

View on GitHub (pinned to 67001ec6eb)

Solutions

  1. Provide each entry as either a string ("/usr/lib") or an object ({ path: "/usr/lib", access: "ro" }).
  2. Filter the array before parsing to drop nulls: entries.filter((e) => e != null).
  3. Quote YAML entries that look like numbers or booleans to force string parsing.
  4. Validate the array shape with a schema (zod z.array(z.union([z.string(), z.object({ path: z.string(), access: z.enum(["ro","rw"]).optional() })]))) at the config boundary.

Example fix

// before
parseLocalProcessSandboxExtraPaths(["/usr/lib", 42, { path: "/etc", access: "ro" }]);

// after
parseLocalProcessSandboxExtraPaths(["/usr/lib", "/opt/data", { path: "/etc", access: "ro" }]);
Defensive patterns

Strategy: type-guard

Validate before calling

function isExtraPathEntry(value: unknown): value is string | { path: string; access?: string } {
  if (typeof value === "string") return true;
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
  const raw = value as Record<string, unknown>;
  return typeof raw.path === "string" && (raw.access === undefined || raw.access === "ro" || raw.access === "rw");
}

if (!Array.isArray(config.filesystemExtraPaths) || !config.filesystemExtraPaths.every(isExtraPathEntry)) {
  throw new Error("filesystemExtraPaths must be string[] or { path, access? }[]");
}
const parsed = parseLocalProcessSandboxExtraPaths(config.filesystemExtraPaths);

Type guard

function isLocalProcessSandboxExtraPath(value: unknown): value is string | { path: string; access?: 'ro' | 'rw' } {
  if (typeof value === "string") return true;
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
  const raw = value as Record<string, unknown>;
  return typeof raw.path === "string" && (raw.access === undefined || raw.access === "ro" || raw.access === "rw");
}

Try / catch

try {
  const parsed = parseLocalProcessSandboxExtraPaths(config.filesystemExtraPaths);
} catch (error) {
  if (error instanceof Error && error.message.startsWith("filesystemExtraPaths[")) {
    throw new ConfigError(`Invalid filesystemExtraPaths: ${error.message}`, { cause: error });
  }
  throw error;
}

Prevention

When it happens

Trigger: filesystemExtraPaths: ["/usr/lib", 42, true, null, ["/etc"], new URL("file:///etc")] — every non-string, non-plain-object entry hits the throw at local-process-sandbox.ts:499-501. Arrays are explicitly rejected via Array.isArray(entry).

Common situations: Config loaded from YAML where unquoted values become numbers or booleans; JSON payloads with mixed shapes; a default value that leaks null into the array; or callers that pass a single object instead of an array of objects.

Related errors


AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12). Data as JSON: /api/errors/3bb3a7702a9e7b0d. Report an issue: GitHub.