paperclipai/paperclip · error · Error
Registered source Paperclip config escapes the base project
Error message
Registered source Paperclip config escapes the base project workspace or uses a symlink alias.
What it means
The base workspace declares its own config, so the expected source is `<base>/.paperclip/config.json`, but the canonical path of that selection differs from the registered joined path. That means some component (typically a symlinked `.paperclip` directory) resolves outside the base workspace, so the 'registered' config is actually stored elsewhere — rejected as an escape/alias.
Source
Thrown at packages/shared/src/worktree-seed-source.ts:156
if (!lstatSync(canonicalBaseCwd).isDirectory()) {
throw new Error(`Registered base project workspace is not a directory at ${canonicalBaseCwd}.`);
}
// A base workspace that is a plain checkout carries no instance config of its own.
// The caller's explicit source supplies it, and stays subject to every check below.
registeredConfigPath = baseWorkspaceDeclaresInstanceConfig(canonicalBaseCwd)
? path.join(canonicalBaseCwd, ".paperclip", "config.json")
: null;
}
const selectedPath = registeredConfigPath ?? explicitSource;
if (!selectedPath) {
throw new Error(
"Registered base project workspace has no Paperclip config of its own and no explicit source was provided.",
);
}
const canonicalSourceConfigPath = canonicalRegularFile(selectedPath, "Registered source Paperclip config");
if (registeredConfigPath && canonicalSourceConfigPath !== registeredConfigPath) {
throw new Error("Registered source Paperclip config escapes the base project workspace or uses a symlink alias.");
}
if (explicitSource) {
const canonicalExplicitSource = canonicalRegularFile(explicitSource, "Explicit source Paperclip config");
if (canonicalExplicitSource !== canonicalSourceConfigPath) {
throw new Error("Explicit source Paperclip config does not match the registered base project workspace.");
}
}
const canonicalTargetConfigPath = canonicalRegularFile(
input.targetConfigPath,
"Target worktree Paperclip config",
);
if (canonicalSourceConfigPath === canonicalTargetConfigPath) {
throw new Error("Source and target Paperclip configs are the same canonical file.");
}
const sourceInstanceId = readInstanceId(canonicalSourceConfigPath, "source");View on GitHub (pinned to a7e689b3c3)
Solutions
- Check for links along the path: `namei -l <base>/.paperclip/config.json`.
- Replace the symlinked `.paperclip` with a real directory containing a real config.json inside the workspace.
- If sharing one instance across worktrees is the goal, use the supported explicit --from-config flow instead of symlinking `.paperclip`.
Example fix
# before ln -s ~/.paperclip-shared ~/code/myproj/.paperclip # escapes base workspace # after rm ~/code/myproj/.paperclip mkdir ~/code/myproj/.paperclip cp ~/.paperclip-shared/config.json ~/code/myproj/.paperclip/config.json
Defensive patterns
Strategy: validation
Validate before calling
import { lstatSync, realpathSync } from "node:fs";
import path from "node:path";
function configStaysInsideBase(base: string): boolean {
const joined = path.join(base, ".paperclip", "config.json");
try { return realpathSync(joined) === joined; } catch { return false; }
}
const st = lstatSync(path.join(base, ".paperclip"), { throwIfNoEntry: false });
if (st?.isSymbolicLink()) throw new Error(".paperclip must not be a symlink"); Type guard
const isContainedRealPath = (base: string, rel: string): boolean => {
const joined = path.join(base, rel);
try { return realpathSync(joined) === joined; } catch { return false; }
}; Try / catch
try {
resolveRegisteredWorktreeSeedSource(input);
} catch (e) {
if (e instanceof Error && /escapes the base project workspace/.test(e.message)) {
// materialize .paperclip as a real dir with a copied config, then retry
} else throw e;
} Prevention
- Treat `.paperclip` as instance-private state: real directory, never a link.
- Ban dotfile-stow tools from managing `.paperclip` in repo dotfiles config.
- Add a repo lint that fails when `.paperclip` is a symlink.
When it happens
Trigger: `.paperclip` being a symlink to a config directory in another location (shared across checkouts); a parent path component of the config being a link; canonicalRegularFile returning a realpath that differs from path.join(base, '.paperclip', 'config.json').
Common situations: Sharing one instance config among multiple clones via `ln -s` (a workflow the invariant explicitly forbids); dotfiles managers (stow, chezmoi) symlinking `.paperclip`; container layers linking config into the workspace.
Related errors
- Registered base project workspace Paperclip config at ${conf
- ${label} does not exist at ${resolved}.
- ${label} must be a canonical path and cannot use a symlink a
- ${label} is not a regular file at ${canonical}.
- Worktree seed source is not registered. Managed boot require
AI-assisted analysis of paperclipai/paperclip@a7e689b3c3 (2026-08-21).
Data as JSON: /api/errors/620cb0b313a09033.
Report an issue: GitHub.