paperclipai/paperclip · warning
[paperclip] shapePaperclipWorkspaceEnvForExecution called…
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.
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
- Could not clean managed GitHub launchers
- Could not prepare managed GitHub launchers
- Could not read execution-target Git context
- Could not resolve remote PATH for managed GitHub launchers
- CreateOS process stream reported an error.
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-08-18).
Data as JSON: /api/errors/8b7d8827964e9838.
Report an issue: GitHub.
Appendix: source
Thrown at packages/adapter-utils/src/server-utils.ts:3136
const apiUrl =
process.env.PAPERCLIP_API_URL ??
process.env.PAPERCLIP_RUNTIME_API_URL ??
`http://${runtimeHost}:${runtimePort}`;
vars.PAPERCLIP_API_URL = apiUrl;
return vars;
}
export function applyPaperclipWorkspaceEnv(
env: Record<string, string>,
input: {
workspaceCwd?: string | null;
workspaceSource?: string | null;
workspaceStrategy?: string | null;
workspaceId?: string | null;
workspaceRepoUrl?: string | null;
workspaceRepoRef?: string | null;
workspaceBranch?: string | null;
workspaceWorktreePath?: string | null;
agentHome?: string | null;
},
): Record<string, string> {
const mappings = [
["PAPERCLIP_WORKSPACE_CWD", input.workspaceCwd],
["PAPERCLIP_WORKSPACE_SOURCE", input.workspaceSource],
["PAPERCLIP_WORKSPACE_STRATEGY", input.workspaceStrategy],
["PAPERCLIP_WORKSPACE_ID", input.workspaceId],
["PAPERCLIP_WORKSPACE_REPO_URL", input.workspaceRepoUrl],
["PAPERCLIP_WORKSPACE_REPO_REF", input.workspaceRepoRef],
["PAPERCLIP_WORKSPACE_BRANCH", input.workspaceBranch],
["PAPERCLIP_WORKSPACE_WORKTREE_PATH", input.workspaceWorktreePath],
["AGENT_HOME", input.agentHome],
] as const;
for (const [key, value] of mappings) {
if (typeof value === "string" && value.length > 0) {
env[key] = value;View on GitHub (pinned to 3f1d897a7c)