paperclipai/paperclip · warning
[paperclip] shapePaperclipWorkspaceEnvForExecution called wi
Error message
[paperclip] shapePaperclipWorkspaceEnvForExecution called with executionCwd=null on a remote target; stripping workspaceCwd to avoid leaking local paths into the remote environment.
What it means
shapePaperclipWorkspaceEnvForExecution rewrites workspace paths (workspaceCwd, hint cwds) so host-local paths never leak into a remote execution environment. Its contract: when executionTargetIsRemote is true, callers must pass a non-empty executionCwd (resolved via adapterExecutionTargetRemoteCwd, which always returns a non-empty string). When executionCwd is null/blank on a remote target, the helper strips workspaceCwd entirely and emits this warning so the contract violation is visible instead of silently leaking paths.
Source
Thrown at packages/adapter-utils/src/server-utils.ts:2182
workspaceCwd,
workspaceWorktreePath,
workspaceHints,
};
}
const executionCwd =
typeof input.executionCwd === "string" && input.executionCwd.trim().length > 0
? input.executionCwd.trim()
: null;
// On a remote target we must never fall back to the local workspaceCwd —
// doing so leaks host paths into the remote env (the exact failure mode
// this helper exists to prevent). Callers are expected to resolve
// executionCwd via adapterExecutionTargetRemoteCwd before calling this
// helper, which always returns a non-empty string. Surface a warning so
// future callers don't silently regress to the leak.
if (executionCwd === null) {
// eslint-disable-next-line no-console
console.warn(
"[paperclip] shapePaperclipWorkspaceEnvForExecution called with executionCwd=null on a remote target; " +
"stripping workspaceCwd to avoid leaking local paths into the remote environment.",
);
}
const realizedWorkspaceCwd = executionCwd;
const localWorkspaceCwd = workspaceCwd ? path.resolve(workspaceCwd) : null;
const stagedProjectDirs = input.stagedProjectDirs ?? {};
const shapedWorkspaceHints = workspaceHints.map((hint) => {
const nextHint = { ...hint };
const hintCwd = typeof nextHint.cwd === "string" ? nextHint.cwd.trim() : "";
if (!hintCwd) return nextHint;
if (localWorkspaceCwd && path.resolve(hintCwd) === localWorkspaceCwd) {
if (realizedWorkspaceCwd) {
nextHint.cwd = realizedWorkspaceCwd;
} else {
delete nextHint.cwd;
}View on GitHub (pinned to 120ae5428f)
Solutions
- In the caller, resolve the remote cwd first: const executionCwd = await adapterExecutionTargetRemoteCwd(...), and pass it as executionCwd.
- Assert non-empty before calling: if (!executionCwd) throw — fail fast at the call site instead of accepting a degraded env.
- Upgrade the adapter package if a released version has this bug and a fix exists.
- Grep for call sites passing executionTargetIsRemote and audit each one's executionCwd provenance.
Example fix
// before
const shaped = shapePaperclipWorkspaceEnvForExecution({
workspaceCwd,
workspaceHints,
executionTargetIsRemote: target.kind === "remote",
executionCwd: null, // warns: strips workspaceCwd
});
// after
const executionCwd = await adapterExecutionTargetRemoteCwd(target, workspace);
if (!executionCwd) throw new Error("Failed to resolve remote execution cwd");
const shaped = shapePaperclipWorkspaceEnvForExecution({
workspaceCwd,
workspaceHints,
executionTargetIsRemote: target.kind === "remote",
executionCwd,
}); Defensive patterns
Strategy: validation
Validate before calling
const executionCwd =
input.executionTargetIsRemote
? await adapterExecutionTargetRemoteCwd(input.executionTarget)
: null;
if (input.executionTargetIsRemote && !executionCwd) {
throw new Error("executionCwd must be resolved (non-empty) for remote targets");
}
const shaped = shapePaperclipWorkspaceEnvForExecution({ ...input, executionCwd }); Type guard
const isNonEmptyString = (v: unknown): v is string => typeof v === "string" && v.trim().length > 0; // gate the call: if (executionTargetIsRemote && !isNonEmptyString(executionCwd)) fail fast
Prevention
- Always resolve executionCwd via adapterExecutionTargetRemoteCwd before shaping env for remote targets.
- Fail fast at the call site when remote executionCwd is missing, instead of accepting the stripped-env degradation.
- In adapter tests, assert no warning fires when executionTargetIsRemote is true (regression guard for the contract).
- Audit every call site passing executionTargetIsRemote for executionCwd provenance after upgrading adapter-utils.
When it happens
Trigger: An adapter calls the helper with executionTargetIsRemote: true but omits executionCwd or passes an empty/whitespace string — e.g. a caller that skipped adapterExecutionTargetRemoteCwd, or a new call site added without resolving the remote cwd first.
Common situations: New adapters or patched call sites forgetting the remote-cwd resolution step; executionCwd computed conditionally and the falsy branch slipping through; upgrading adapter-utils where the contract became enforced.
Related errors
AI-assisted analysis of paperclipai/paperclip@120ae5428f (2026-08-18).
Data as JSON: /api/errors/8b7d8827964e9838.
Report an issue: GitHub.