paperclipai/paperclip · error

Sandbox path alias "${aliasPath}" must target the synchroniz

Error message

Sandbox path alias "${aliasPath}" must target the synchronized workspace "${workspaceDir}".

What it means

Thrown in the pathAliases loop of buildLocalProcessSandboxSpawnTarget when path.relative(workspaceDir, alias.target) escapes the workspace. Path aliases bind a host path (alias.target) to an in-sandbox path (alias.path) via bwrap --bind; only targets inside the synchronized workspace are allowed, because anything else would either duplicate content the sandbox cannot see or diverge from the synced tree.

Source

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

      addParentDirectories(args, created, normalized);
      args.push(access === "rw" ? "--bind" : "--ro-bind", normalized, normalized);
      mounted.add(normalized);
      created.add(normalized);
    };
    for (const systemPath of SYSTEM_READ_PATHS) await mount(systemPath, "ro");
    for (const executablePath of await executableReadPaths(input.executable)) await mount(executablePath, "ro");
    if (networkScope === "allowlist") {
      for (const nodePath of await executableReadPaths(process.execPath)) await mount(nodePath, "ro");
    }
    for (const managedPath of input.options.managedPaths ?? []) await mount(managedPath.path, managedPath.access);
    for (const extraPath of input.options.extraPaths ?? []) await mount(extraPath.path, extraPath.access);
    await mount(workspaceDir, "rw");
    for (const [index, alias] of (input.options.pathAliases ?? []).entries()) {
      const aliasPath = normalizeAbsolutePath(alias.path, `Sandbox pathAliases[${index}].path`);
      const aliasTarget = normalizeAbsolutePath(alias.target, `Sandbox pathAliases[${index}].target`);
      const relativeTarget = path.relative(workspaceDir, aliasTarget);
      if (relativeTarget.startsWith("..") || path.isAbsolute(relativeTarget)) {
        throw new Error(
          `Sandbox path alias "${aliasPath}" must target the synchronized workspace "${workspaceDir}".`,
        );
      }
      if (!(await pathExists(aliasTarget))) {
        throw new Error(`Sandbox path alias target "${aliasTarget}" does not exist.`);
      }
      addParentDirectories(args, created, aliasPath);
      args.push("--bind", aliasTarget, aliasPath);
      created.add(aliasPath);
    }

    if (networkScope === "allowlist") {
      const tempDir = await createNetworkProxyTempDir();
      const socketPath = path.join(tempDir, "proxy.sock");
      const bridgePath = path.join(tempDir, "bridge.cjs");
      await fs.writeFile(bridgePath, await createNetworkProxyBridge(), { mode: 0o500 });
      const proxy = await startNetworkAllowlistProxy(
        input.options.networkAllowlist ?? [],

View on GitHub (pinned to 67001ec6eb)

Solutions

  1. Move the alias target inside workspaceDir, or expand workspaceDir to cover the target's parent.
  2. If the target must live outside the workspace and is read-only, expose it via extraPaths with access "ro" instead of pathAliases (extraPaths is not bound to the workspace-only-target rule).
  3. Resolve symlinks on alias.target before sandbox spawn — an in-workspace symlink that resolves outside is the typical surprise.
  4. Re-check each alias.target against path.relative(workspaceDir, aliasTarget) in your config validator so the failure surfaces at load time.

Example fix

// before
pathAliases: [{ path: "/work/.env", target: "/home/user/.env" }]
// workspaceDir = "/srv/work"

// after: copy .env into the workspace before sandboxing
await fs.copyFile("/home/user/.env", "/srv/work/.env");
pathAliases: [{ path: "/work/.env", target: "/srv/work/.env" }]
Defensive patterns

Strategy: validation

Validate before calling

function assertPathAliasesInWorkspace(workspaceDir: string, aliases: { path: string; target: string }[]): void {
  for (const alias of aliases) {
    const rel = path.relative(workspaceDir, alias.target);
    if (rel.startsWith("..") || path.isAbsolute(rel)) {
      throw new Error(`Alias ${alias.path} target ${alias.target} outside workspace ${workspaceDir}`);
    }
  }
}

const realWorkspace = await fs.realpath(workspaceDir);
const realAliases = await Promise.all(aliases.map(async (a) => ({ ...a, target: await fs.realpath(a.target) })));
assertPathAliasesInWorkspace(realWorkspace, realAliases);

Try / catch

try {
  return await buildLocalProcessSandboxSpawnTarget(input);
} catch (error) {
  if (error instanceof Error && error.message.includes("must target the synchronized workspace")) {
    throw new ConfigError(`Path alias target outside workspace. Move target inside ${workspaceDir} or use extraPaths with access ro.`, { cause: error });
  }
  throw error;
}

Prevention

When it happens

Trigger: pathAliases: [{ path: "/work/node_modules", target: "/host/home/user/node_modules" }] with workspaceDir: "/srv/work". The check at local-process-sandbox.ts:417-421 fires for each alias whose target is outside the workspace (starts with ".." or is absolute-relative).

Common situations: Adapters that try to alias host home-directory dotfiles, system-wide caches, or symlinked dependencies that live outside the project. Also seen when workspaceDir is set to a subdirectory but aliases target files in the project root one level up.

Related errors


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