{"record":{"id":"16244b1bf8aa7dde","repo":"paperclipai/paperclip","slug":"invalid-github-launcher-run-id","errorCode":null,"errorMessage":"Invalid GitHub launcher run ID","messagePattern":"Invalid GitHub launcher run ID","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/adapter-utils/src/execution-target.ts","lineNumber":1529,"sourceCode":"    restoreWorkspace: prepared.restoreWorkspace,\n  };\n}\n\nexport function runtimeAssetDir(\n  prepared: Pick<PreparedAdapterExecutionTargetRuntime, \"assetDirs\">,\n  key: string,\n  fallbackRemoteCwd: string,\n): string {\n  return prepared.assetDirs[key] ?? path.posix.join(fallbackRemoteCwd, \".paperclip-runtime\", key);\n}\n\ntype GitHubLauncherLocation = {\n  runId: string; target: AdapterExecutionTarget | null | undefined;\n};\n\nfunction githubOperationLauncherDirectory(input: GitHubLauncherLocation): string {\n  // Only controller-generated run IDs may name a removable directory.\n  if (!/^[a-zA-Z0-9_-]+$/.test(input.runId)) throw new Error(\"Invalid GitHub launcher run ID\");\n  return input.target?.kind === \"remote\"\n    ? path.posix.join(input.target.remoteCwd, \".paperclip-runtime\", \"github\", input.runId)\n    : path.join(os.tmpdir(), \"paperclip-github-runtime\", input.runId);\n}\n\n/** Call only after execution settles, before releasing its remote environment lease. */\nexport async function cleanupGitHubOperationLaunchers(input: GitHubLauncherLocation): Promise<void> {\n  const directory = githubOperationLauncherDirectory(input);\n  if (input.target?.kind === \"remote\") {\n    const result = await adapterExecutionTargetCommandRunner(input.target).execute({\n      command: \"sh\", args: [\"-c\", `rm -rf -- ${shellQuote(directory)}`],\n      cwd: input.target.remoteCwd, timeoutMs: 5_000,\n    });\n    if (result.exitCode !== 0) throw new Error(\"Could not clean managed GitHub launchers\");\n  } else {\n    await fs.rm(directory, { recursive: true, force: true });\n  }\n}","sourceCodeStart":1511,"sourceCodeEnd":1547,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/adapter-utils/src/execution-target.ts#L1511-L1547","documentation":"githubOperationLauncherDirectory builds the filesystem path for a managed GitHub operation launcher directory but only permits controller-generated run IDs, enforced by the regex /^[a-zA-Z0-9_-]+$/. This guard prevents arbitrary strings (potentially from untrusted run identifiers) from escaping into path construction. An ID containing slashes, dots, spaces, or other characters triggers this error.","triggerScenarios":"Calling the launcher directory/cleanup API with a runId containing '/', '..', whitespace, unicode, or any character outside [a-zA-Z0-9_-]; passing a raw external identifier (e.g. a full GitHub run URL or numeric id with prefix) as runId.","commonSituations":"Downstream code concatenating identifiers like 'runs/12345' or 'run.id' instead of the sanitized controller run ID; test code passing placeholder IDs with slashes.","solutions":["Pass only the controller-generated run ID matching [a-zA-Z0-9_-]+","Sanitize/normalize the identifier upstream, e.g. runId.replace(/[^a-zA-Z0-9_-]/g, '')","Verify the value is not a URL or path fragment — extract the bare ID first"],"exampleFix":"// before\ncleanup({ runId: githubRun.url.split('/').pop() + '/artifacts' })\n// after\nconst runId = githubRun.id.toString();\nif (!/^[a-zA-Z0-9_-]+$/.test(runId)) throw new Error('bad run id');\ncleanup({ runId, target });","handlingStrategy":"type-guard","validationCode":"if (!/^[a-zA-Z0-9_-]+$/.test(runId)) {\n  throw new Error(`runId must match [a-zA-Z0-9_-]+, got: ${runId}`);\n}","typeGuard":"function isSafeRunId(v: unknown): v is string {\n  return typeof v === 'string' && /^[a-zA-Z0-9_-]+$/.test(v);\n}","tryCatchPattern":"try {\n  cleanupLauncher({ runId, target });\n} catch (e) {\n  if (String(e.message) === 'Invalid GitHub launcher run ID') {\n    console.error('sanitize the run ID before calling cleanup');\n  } else throw e;\n}","preventionTips":["Only pass controller-generated run IDs, never URLs or composite identifiers","Sanitize external IDs at ingestion: id.replace(/[^a-zA-Z0-9_-]/g, '')","Add a unit test asserting launcher directory calls reject unsafe IDs"],"tags":["validation","security","path-safety"],"backgroundTag":"invalid-identifier-format","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"}