{"record":{"id":"304ecfc0a36da962","repo":"paperclipai/paperclip","slug":"could-not-prepare-managed-github-launchers","errorCode":null,"errorMessage":"Could not prepare managed GitHub launchers","messagePattern":"Could not prepare managed GitHub launchers","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/adapter-utils/src/execution-target.ts","lineNumber":1714,"sourceCode":"  // the managed launchers after startup without loading a host user's profile.\n  const profile = `export PATH=${shellQuote(managedPath)}\\n`;\n  const files: Record<string, string> = Object.fromEntries([\n    ...[\"git\", \"gh\"].map((name) => [name, githubLauncherSource()] as const),\n    ...[\".zshenv\", \".zprofile\", \".zshrc\", \".bash_profile\", \".bashrc\", \".profile\"].map((name) => [name, profile] as const),\n  ]);\n  if (remote) {\n    const runner = adapterExecutionTargetCommandRunner(remote);\n    for (const [program, body] of Object.entries(files)) {\n      await syncRemoteTextFileWithHashSkip({\n        runner, remoteCwd: remote.remoteCwd, remoteDir: directory,\n        remotePath: path.posix.join(directory, program), body,\n        label: \"GitHub operation launcher\", action: \"stage GitHub operation launcher\",\n        lockDir: path.posix.join(directory, `.${program}.lock`),\n        timeoutMs: 15_000, shellCommand: adapterExecutionTargetShellCommand(remote),\n      });\n    }\n    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 });\n    if (permissions.exitCode !== 0) throw new Error(\"Could not prepare managed GitHub launchers\");\n  } else {\n    await fs.mkdir(directory, { recursive: true, mode: 0o700 });\n    await fs.mkdir(configDirectory, { recursive: true, mode: 0o700 });\n    for (const [program, body] of Object.entries(files)) await fs.writeFile(path.join(directory, program), body, { mode: 0o700 });\n  }\n  return { ...input.env, PATH: managedPath, ZDOTDIR: directory, BASH_ENV: `${directory}/.bashrc`,\n    GH_CONFIG_DIR: configDirectory, PAPERCLIP_GITHUB_LAUNCHER_DIR: directory };\n}\n\nfunction buildBridgeResponseHeaders(response: Response): Record<string, string> {\n  const out: Record<string, string> = {};\n  // Keep `x-paperclip-bridge-outcome` in this list. The host marks a\n  // possibly-committed mutation with the `indeterminate` outcome. The in-sandbox\n  // server reads that header to map the 504 to a terminal 409. If the forward\n  // drops the header, the server keeps the retryable 504 and a caller that\n  // retries 5xx can repeat a mutation that already committed.\n  for (const key of [\"content-type\", \"etag\", \"last-modified\", \"x-paperclip-bridge-outcome\"]) {\n    const value = response.headers.get(key);","sourceCodeStart":1696,"sourceCodeEnd":1732,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/adapter-utils/src/execution-target.ts#L1696-L1732","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Exec into the target and run the chmod/mkdir command manually to see the failing sub-command and errno.","Verify the remote user owns <directory> and that remote.remoteCwd exists and is writable.","Check the mount is not read-only or mounted noexec (launchers must be executable).","Re-run prepare to re-stage the launcher files if they were removed by a concurrent cleanup.","Confirm 'sh' exists on the target and the lock dirs (.git.lock/.gh.lock) are not held by a stale process."],"exampleFix":"// before\nif (permissions.exitCode !== 0) throw new Error(\"Could not prepare managed GitHub launchers\");\n// after\nif (permissions.exitCode !== 0) throw new Error(`Could not prepare managed GitHub launchers (exit ${permissions.exitCode}): ${permissions.stderr.slice(0, 500)}`);","handlingStrategy":"try-catch","validationCode":"const check = await runner.execute({ command: \"sh\", args: [\"-c\", `test -w ${shellQuote(directory)} && command -v chmod`], cwd: remote.remoteCwd, timeoutMs: 5_000 });\nif (check.exitCode !== 0) throw new Error(`remote launcher dir not writable: ${directory}`);","typeGuard":"null","tryCatchPattern":"try {\n  await prepareManagedGithubLaunchers(/* ... */);\n} catch (err) {\n  if (String(err.message).includes(\"managed GitHub launchers\")) {\n    log.error(\"chmod/mkdir failed on target; check ownership and mount flags\", { remoteCwd: remote.remoteCwd });\n  }\n  throw err;\n}","preventionTips":["Pre-create the launcher directory on the target with the same UID the runner executes as.","Avoid noexec/read-only mounts for the launcher directory location.","Stale .git.lock/.gh.lock dirs should be cleaned between runs.","Run the exact chmod/mkdir line manually once when provisioning a new target."],"tags":["permissions","github","remote-execution","chmod"],"backgroundTag":"file-write-permission-denied","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}