{"record":{"id":"8b7d8827964e9838","repo":"paperclipai/paperclip","slug":"paperclip-shapepaperclipworkspaceenvforexecution","errorCode":null,"errorMessage":"[paperclip] shapePaperclipWorkspaceEnvForExecution called with executionCwd=null on a remote target; stripping workspaceCwd to avoid leaking local paths into the remote environment.","messagePattern":"\\[paperclip\\] shapePaperclipWorkspaceEnvForExecution called with executionCwd=null on a remote target; stripping workspaceCwd to avoid leaking local paths into the remote environment\\.","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/adapter-utils/src/server-utils.ts","lineNumber":2182,"sourceCode":"      workspaceCwd,\n      workspaceWorktreePath,\n      workspaceHints,\n    };\n  }\n\n  const executionCwd =\n    typeof input.executionCwd === \"string\" && input.executionCwd.trim().length > 0\n      ? input.executionCwd.trim()\n      : null;\n  // On a remote target we must never fall back to the local workspaceCwd —\n  // doing so leaks host paths into the remote env (the exact failure mode\n  // this helper exists to prevent). Callers are expected to resolve\n  // executionCwd via adapterExecutionTargetRemoteCwd before calling this\n  // helper, which always returns a non-empty string. Surface a warning so\n  // future callers don't silently regress to the leak.\n  if (executionCwd === null) {\n    // eslint-disable-next-line no-console\n    console.warn(\n      \"[paperclip] shapePaperclipWorkspaceEnvForExecution called with executionCwd=null on a remote target; \" +\n        \"stripping workspaceCwd to avoid leaking local paths into the remote environment.\",\n    );\n  }\n  const realizedWorkspaceCwd = executionCwd;\n  const localWorkspaceCwd = workspaceCwd ? path.resolve(workspaceCwd) : null;\n  const stagedProjectDirs = input.stagedProjectDirs ?? {};\n  const shapedWorkspaceHints = workspaceHints.map((hint) => {\n    const nextHint = { ...hint };\n    const hintCwd = typeof nextHint.cwd === \"string\" ? nextHint.cwd.trim() : \"\";\n    if (!hintCwd) return nextHint;\n\n    if (localWorkspaceCwd && path.resolve(hintCwd) === localWorkspaceCwd) {\n      if (realizedWorkspaceCwd) {\n        nextHint.cwd = realizedWorkspaceCwd;\n      } else {\n        delete nextHint.cwd;\n      }","sourceCodeStart":2164,"sourceCodeEnd":2200,"githubUrl":"https://github.com/paperclipai/paperclip/blob/120ae5428fa29bee300bcf806491cd4d965fbb7c/packages/adapter-utils/src/server-utils.ts#L2164-L2200","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst shaped = shapePaperclipWorkspaceEnvForExecution({\n  workspaceCwd,\n  workspaceHints,\n  executionTargetIsRemote: target.kind === \"remote\",\n  executionCwd: null, // warns: strips workspaceCwd\n});\n\n// after\nconst executionCwd = await adapterExecutionTargetRemoteCwd(target, workspace);\nif (!executionCwd) throw new Error(\"Failed to resolve remote execution cwd\");\nconst shaped = shapePaperclipWorkspaceEnvForExecution({\n  workspaceCwd,\n  workspaceHints,\n  executionTargetIsRemote: target.kind === \"remote\",\n  executionCwd,\n});","handlingStrategy":"validation","validationCode":"const executionCwd =\n  input.executionTargetIsRemote\n    ? await adapterExecutionTargetRemoteCwd(input.executionTarget)\n    : null;\nif (input.executionTargetIsRemote && !executionCwd) {\n  throw new Error(\"executionCwd must be resolved (non-empty) for remote targets\");\n}\nconst shaped = shapePaperclipWorkspaceEnvForExecution({ ...input, executionCwd });","typeGuard":"const isNonEmptyString = (v: unknown): v is string =>\n  typeof v === \"string\" && v.trim().length > 0;\n// gate the call: if (executionTargetIsRemote && !isNonEmptyString(executionCwd)) fail fast","tryCatchPattern":null,"preventionTips":["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."],"tags":["adapter-utils","remote-execution","env-hygiene","contract-violation","path-leak"],"backgroundTag":"missing-execution-cwd","analyzedSha":"120ae5428fa29bee300bcf806491cd4d965fbb7c","analyzedAt":"2026-08-18T22:49:45.177Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}