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

  1. In the caller, resolve the remote cwd first: const executionCwd = await adapterExecutionTargetRemoteCwd(...), and pass it as executionCwd.
  2. Assert non-empty before calling: if (!executionCwd) throw — fail fast at the call site instead of accepting a degraded env.
  3. Upgrade the adapter package if a released version has this bug and a fix exists.
  4. 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

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.