paperclipai/paperclip · error

Remote Codex working directory does not match the assigned…

Error message

Remote Codex working directory does not match the assigned workspace

What it means

For remote runners, the controller cannot inspect the provider-owned filesystem, so the facade is pinned to exactly the assigned workspace: the requested working directory must equal the trimmed PAPERCLIP_WORKSPACE_CWD string. This throws when the two differ in any way, meaning the caller attempted to run Codex outside the single assigned remote workspace.

Solutions

  1. Pass exactly process.env.PAPERCLIP_WORKSPACE_CWD (trimmed) as the remote working directory — the workspace root, not a subpath.
  2. Regenerate the run config after workspace reassignment so the recorded working directory matches the currently assigned workspace.
  3. Convert local paths to the remote workspace path before remote validation; never resolve the remote path on the local filesystem.
  4. Compare with string equality in your own code first (assert(workingDirectory === env.PAPERCLIP_WORKSPACE_CWD?.trim())) to catch drift early.

Example fix

// before
const cwd = realpathSync.native(process.env.PAPERCLIP_WORKSPACE_CWD + "/subdir");
validateCodexWorkingDirectory(cwd, process.env, "remote_runner");
// after
const cwd = process.env.PAPERCLIP_WORKSPACE_CWD!.trim();
validateCodexWorkingDirectory(cwd, process.env, "remote_runner");
Defensive patterns

Strategy: validation

Validate before calling

const configuredRoot = process.env.PAPERCLIP_WORKSPACE_CWD?.trim();
if (workingDirectory !== configuredRoot) {
  throw new Error(`remote cwd must equal assigned workspace: ${workingDirectory} !== ${configuredRoot}`);
}

Try / catch

try {
  validateCodexWorkingDirectory(workingDirectory, process.env, "remote_runner");
} catch (error) {
  if (error instanceof Error && error.message.includes("does not match the assigned workspace")) {
    console.error(`cwd=${workingDirectory} workspace=${process.env.PAPERCLIP_WORKSPACE_CWD}`);
  }
  throw error;
}

Prevention

When it happens

Trigger: validateCodexWorkingDirectory("/workspaces/other", env, "remote_runner") while env.PAPERCLIP_WORKSPACE_CWD === "/workspaces/issue-42"; also string mismatches like case differences, a trailing slash on one side, or passing the local resolved path instead of the remote workspace path.

Common situations: A controller reusing a local filesystem path (e.g. C:\repo or /home/dev/repo) against a remote runner; a task attempting to cd into a subdirectory of the workspace instead of the workspace root; stale run config referencing a previous workspace after reassignment; locale/case normalization discrepancies on case-insensitive providers.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/992b473a31fcbbcd. Report an issue: GitHub.

Appendix: source

Thrown at packages/paperclip-runner/src/drivers/codex/codex-boundaries.ts:154

  const configuredRoot = environment.PAPERCLIP_WORKSPACE_CWD?.trim();
  if (!configuredRoot) {
    throw new Error(
      "Remote Codex working directory requires an assigned workspace",
    );
  }
  if (
    !posix.isAbsolute(configuredRoot) ||
    posix.normalize(configuredRoot) !== configuredRoot
  ) {
    throw new Error(
      "Assigned remote workspace must be a normalized absolute path",
    );
  }
  // The controller cannot inspect a provider-owned filesystem. Pin the facade
  // to the exact remote workspace while runnerd validates existence, type, and
  // canonical identity inside the authoritative filesystem before launch.
  if (workingDirectory !== configuredRoot) {
    throw new Error(
      "Remote Codex working directory does not match the assigned workspace",
    );
  }
  return workingDirectory;
}

function canonicalConfiguredPath(value: string | undefined): string | null {
  const configured = value?.trim();
  if (!configured) return null;
  return canonicalPathWithMissingTail(resolve(configured));
}

function canonicalPathWithMissingTail(path: string): string {
  let cursor = path;
  const missing: string[] = [];
  while (true) {
    try {
      return resolve(realpathSync.native(cursor), ...missing);

View on GitHub (pinned to 01ad858492)