paperclipai/paperclip · error · Error

Could not prepare managed GitHub launchers

Error message

Could not prepare managed GitHub launchers

What it means

When provisioning managed GitHub launchers (git/gh shell wrappers) on a remote execution target, the module stages launcher files then runs `chmod 700 .../git .../gh && mkdir -p <configDir>`. This error is thrown when that chmod/mkdir command exits non-zero, so the launcher directory is unusable for GitHub operations.

Source

Thrown at packages/adapter-utils/src/execution-target.ts:1714

  // the managed launchers after startup without loading a host user's profile.
  const profile = `export PATH=${shellQuote(managedPath)}\n`;
  const files: Record<string, string> = Object.fromEntries([
    ...["git", "gh"].map((name) => [name, githubLauncherSource()] as const),
    ...[".zshenv", ".zprofile", ".zshrc", ".bash_profile", ".bashrc", ".profile"].map((name) => [name, profile] as const),
  ]);
  if (remote) {
    const runner = adapterExecutionTargetCommandRunner(remote);
    for (const [program, body] of Object.entries(files)) {
      await syncRemoteTextFileWithHashSkip({
        runner, remoteCwd: remote.remoteCwd, remoteDir: directory,
        remotePath: path.posix.join(directory, program), body,
        label: "GitHub operation launcher", action: "stage GitHub operation launcher",
        lockDir: path.posix.join(directory, `.${program}.lock`),
        timeoutMs: 15_000, shellCommand: adapterExecutionTargetShellCommand(remote),
      });
    }
    const permissions = await runner.execute({ command: "sh", args: ["-c", `chmod 700 ${shellQuote(directory)}/git ${shellQuote(directory)}/gh && mkdir -p ${shellQuote(configDirectory)}`], cwd: remote.remoteCwd, timeoutMs: 15_000 });
    if (permissions.exitCode !== 0) throw new Error("Could not prepare managed GitHub launchers");
  } else {
    await fs.mkdir(directory, { recursive: true, mode: 0o700 });
    await fs.mkdir(configDirectory, { recursive: true, mode: 0o700 });
    for (const [program, body] of Object.entries(files)) await fs.writeFile(path.join(directory, program), body, { mode: 0o700 });
  }
  return { ...input.env, PATH: managedPath, ZDOTDIR: directory, BASH_ENV: `${directory}/.bashrc`,
    GH_CONFIG_DIR: configDirectory, PAPERCLIP_GITHUB_LAUNCHER_DIR: directory };
}

function buildBridgeResponseHeaders(response: Response): Record<string, string> {
  const out: Record<string, string> = {};
  // Keep `x-paperclip-bridge-outcome` in this list. The host marks a
  // possibly-committed mutation with the `indeterminate` outcome. The in-sandbox
  // server reads that header to map the 504 to a terminal 409. If the forward
  // drops the header, the server keeps the retryable 504 and a caller that
  // retries 5xx can repeat a mutation that already committed.
  for (const key of ["content-type", "etag", "last-modified", "x-paperclip-bridge-outcome"]) {
    const value = response.headers.get(key);

View on GitHub (pinned to 01ad858492)

Solutions

  1. Exec into the target and run the chmod/mkdir command manually to see the failing sub-command and errno.
  2. Verify the remote user owns <directory> and that remote.remoteCwd exists and is writable.
  3. Check the mount is not read-only or mounted noexec (launchers must be executable).
  4. Re-run prepare to re-stage the launcher files if they were removed by a concurrent cleanup.
  5. Confirm 'sh' exists on the target and the lock dirs (.git.lock/.gh.lock) are not held by a stale process.

Example fix

// before
if (permissions.exitCode !== 0) throw new Error("Could not prepare managed GitHub launchers");
// after
if (permissions.exitCode !== 0) throw new Error(`Could not prepare managed GitHub launchers (exit ${permissions.exitCode}): ${permissions.stderr.slice(0, 500)}`);
Defensive patterns

Strategy: try-catch

Validate before calling

const check = await runner.execute({ command: "sh", args: ["-c", `test -w ${shellQuote(directory)} && command -v chmod`], cwd: remote.remoteCwd, timeoutMs: 5_000 });
if (check.exitCode !== 0) throw new Error(`remote launcher dir not writable: ${directory}`);

Type guard

null

Try / catch

try {
  await prepareManagedGithubLaunchers(/* ... */);
} catch (err) {
  if (String(err.message).includes("managed GitHub launchers")) {
    log.error("chmod/mkdir failed on target; check ownership and mount flags", { remoteCwd: remote.remoteCwd });
  }
  throw err;
}

Prevention

When it happens

Trigger: runner.execute({command:'sh', args:['-c', 'chmod 700 <dir>/git <dir>/gh && mkdir -p <configDir>'], cwd: remote.remoteCwd}) returns exitCode !== 0. Also implied when any earlier staging step (writing launcher files via the same runner) fails before the chmod line.

Common situations: Remote user lacks write/execute permission on the launcher directory or remote.remoteCwd; the directory is on a read-only or noexec mount; the remote 'sh' is missing; the staging directory was deleted between staging and chmod (race with cleanup); disk full preventing file creation.

Understand the failure class

Background: "Permission denied" / "Failed to write" file errors: why a library can't write its files to disk (EACCES, EPERM, ENOSPC) and how to fix them — this error's family across 43 libraries.

Related errors


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