paperclipai/paperclip · critical
Paperclip run authentication is unavailable
Error message
Paperclip run authentication is unavailable
What it means
executeRunnerApi refuses to dispatch an HTTP request when io.token is falsy, throwing "Paperclip run authentication is unavailable". The runner API client requires a Bearer token (local agent JWT) plus the X-Paperclip-Run-Id header for every call; a missing token would be an unauthenticated request, so it fails closed before any network I/O.
Source
Thrown at server/src/services/native-runtime/runner-api-client.ts:144
try {
while (true) {
const next = await reader.read();
if (next.done) break;
bytes += next.value.byteLength;
if (bytes > maxBytes) throw unprocessable("API response exceeds the transfer limit; narrow the request");
chunks.push(next.value);
}
} finally {
await reader.cancel().catch(() => {});
reader.releaseLock();
}
return Buffer.concat(chunks);
}
export async function executeRunnerApi(input: RunnerApiCall, context: RunnerApiContext, io: RunnerApiIo) {
const { operation } = validateRunnerApiCall(input, context);
const url = runnerApiUrl(operation, input, context, io.apiUrl);
if (!io.token) throw new Error("Paperclip run authentication is unavailable");
const headers = new Headers({ Authorization: `Bearer ${io.token}`, "X-Paperclip-Run-Id": context.runId });
let body: BodyInit | undefined;
const contentType = input.contentType ?? (input.files?.length ? "multipart/form-data" : "application/json");
if (/\r|\n/.test(contentType)) throw badRequest("Invalid content type");
let totalBytes = 0;
if (input.files?.length) {
if (["GET", "HEAD"].includes(operation.method)) throw badRequest("Read requests cannot upload files");
const form = new FormData();
if (input.body !== undefined && (!input.body || typeof input.body !== "object" || Array.isArray(input.body))) throw badRequest("Multipart body must be an object of form fields");
for (const [key, value] of Object.entries((input.body ?? {}) as Record<string, unknown>)) {
const text = typeof value === "string" ? value : JSON.stringify(value);
totalBytes += Buffer.byteLength(text);
form.append(key, text);
}
for (const file of input.files) {
const resolved = await io.readFile(file);
totalBytes += resolved.bytes.length;
if (totalBytes > RUNNER_API_MAX_BYTES) throw badRequest("API upload exceeds the transfer limit");View on GitHub (pinned to 01ad858492)
Solutions
- Configure the server JWT signing config so createLocalAgentJwt returns a real token, then retry the run.
- At call sites, guard the token before invoking executeRunnerApi: if (!token) throw/handle before building io.
- In tests, pass an explicit signed test token in RunnerApiIo instead of leaving it undefined.
- Check startup logs for JWT config initialization failures and fix the underlying secret/key setup.
Example fix
// before
await executeRunnerApi(input, context, { apiUrl, token: maybeToken, ... });
// after
if (!maybeToken) throw new Error("Paperclip run authentication is unavailable");
await executeRunnerApi(input, context, { apiUrl, token: maybeToken, ... }); Defensive patterns
Strategy: type-guard
Validate before calling
if (!io.token || typeof io.token !== "string") {
throw new Error("RunnerApiIo.token must be a non-empty JWT before calling executeRunnerApi");
} Type guard
function hasToken(io: RunnerApiIo): io is RunnerApiIo & { token: string } {
return typeof io.token === "string" && io.token.length > 0;
} Try / catch
try {
const res = await executeRunnerApi(input, context, io);
} catch (err) {
if (err instanceof Error && err.message === "Paperclip run authentication is unavailable") {
// re-mint the JWT (fix JWT config) and retry once
} else throw err;
} Prevention
- Always construct RunnerApiIo through a factory that mints the token and throws early if minting fails.
- Check jwtConfig() availability at server startup so token minting can never silently return null.
- In tests, use a helper that always supplies a signed test token to RunnerApiIo.
When it happens
Trigger: executeRunnerApi is invoked with io.token undefined/null/empty — i.e. the caller (e.g. PaperclipRunnerToolAuthority.#callApi) passed the result of createLocalAgentJwt without checking, which is null when the server JWT config is missing; or a direct caller of executeRunnerApi constructs io without a token.
Common situations: Server deployed without JWT signing configuration; test harness calling executeRunnerApi directly with a stub io object omitting token; token-minting code short-circuited after a config change and the null propagated to the client.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Paperclip run authentication is unavailable
- Agent identity is required
- ANTHROPIC_API_KEY is required in the CLI process environment
- device-login promotion: the account identifier cannot form a
- native_adopted_runner_exited
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/b479675ffa766faf.
Report an issue: GitHub.