paperclipai/paperclip · error · Error
Invalid GitHub launcher run ID
Error message
Invalid GitHub launcher run ID
What it means
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.
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
Example fix
// before
cleanup({ runId: githubRun.url.split('/').pop() + '/artifacts' })
// after
const runId = githubRun.id.toString();
if (!/^[a-zA-Z0-9_-]+$/.test(runId)) throw new Error('bad run id');
cleanup({ runId, target }); Defensive patterns
Strategy: type-guard
Validate before calling
if (!/^[a-zA-Z0-9_-]+$/.test(runId)) {
throw new Error(`runId must match [a-zA-Z0-9_-]+, got: ${runId}`);
} Type guard
function isSafeRunId(v: unknown): v is string {
return typeof v === 'string' && /^[a-zA-Z0-9_-]+$/.test(v);
} Try / catch
try {
cleanupLauncher({ runId, target });
} catch (e) {
if (String(e.message) === 'Invalid GitHub launcher run ID') {
console.error('sanitize the run ID before calling cleanup');
} else throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: Downstream code concatenating identifiers like 'runs/12345' or 'run.id' instead of the sanitized controller run ID; test code passing placeholder IDs with slashes.
Understand the failure class
Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.
Related errors
- Cloud control assertion request id is invalid
- Cloud control claims are incomplete
- codex auth cache: account_id is not a valid account handle
- Codex working directory cannot be a filesystem root
- Invalid canonical workspace path
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-10).
Data as JSON: /api/errors/16244b1bf8aa7dde.
Report an issue: GitHub.
Appendix: source
Thrown at packages/adapter-utils/src/execution-target.ts:1543
restoreWorkspace: prepared.restoreWorkspace,
};
}
export function runtimeAssetDir(
prepared: Pick<PreparedAdapterExecutionTargetRuntime, "assetDirs">,
key: string,
fallbackRemoteCwd: string,
): string {
return prepared.assetDirs[key] ?? path.posix.join(fallbackRemoteCwd, ".paperclip-runtime", key);
}
type GitHubLauncherLocation = {
runId: string; target: AdapterExecutionTarget | null | undefined;
};
function githubOperationLauncherDirectory(input: GitHubLauncherLocation): string {
// Only controller-generated run IDs may name a removable directory.
if (!/^[a-zA-Z0-9_-]+$/.test(input.runId)) throw new Error("Invalid GitHub launcher run ID");
return input.target?.kind === "remote"
? path.posix.join(input.target.remoteCwd, ".paperclip-runtime", "github", input.runId)
: path.join(os.tmpdir(), "paperclip-github-runtime", input.runId);
}
/** Call only after execution settles, before releasing its remote environment lease. */
export async function cleanupGitHubOperationLaunchers(input: GitHubLauncherLocation): Promise<void> {
const directory = githubOperationLauncherDirectory(input);
if (input.target?.kind === "remote") {
const result = await adapterExecutionTargetCommandRunner(input.target).execute({
command: "sh", args: ["-c", `rm -rf -- ${shellQuote(directory)}`],
cwd: input.target.remoteCwd, timeoutMs: 5_000,
});
if (result.exitCode !== 0) throw new Error("Could not clean managed GitHub launchers");
} else {
await fs.rm(directory, { recursive: true, force: true });
}
}View on GitHub (pinned to 3f1d897a7c)