{"record":{"id":"b479675ffa766faf","repo":"paperclipai/paperclip","slug":"paperclip-run-authentication-is-unavailable-b47967","errorCode":null,"errorMessage":"Paperclip run authentication is unavailable","messagePattern":"Paperclip run authentication is unavailable","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"server/src/services/native-runtime/runner-api-client.ts","lineNumber":144,"sourceCode":"  try {\n    while (true) {\n      const next = await reader.read();\n      if (next.done) break;\n      bytes += next.value.byteLength;\n      if (bytes > maxBytes) throw unprocessable(\"API response exceeds the transfer limit; narrow the request\");\n      chunks.push(next.value);\n    }\n  } finally {\n    await reader.cancel().catch(() => {});\n    reader.releaseLock();\n  }\n  return Buffer.concat(chunks);\n}\n\nexport async function executeRunnerApi(input: RunnerApiCall, context: RunnerApiContext, io: RunnerApiIo) {\n  const { operation } = validateRunnerApiCall(input, context);\n  const url = runnerApiUrl(operation, input, context, io.apiUrl);\n  if (!io.token) throw new Error(\"Paperclip run authentication is unavailable\");\n  const headers = new Headers({ Authorization: `Bearer ${io.token}`, \"X-Paperclip-Run-Id\": context.runId });\n  let body: BodyInit | undefined;\n  const contentType = input.contentType ?? (input.files?.length ? \"multipart/form-data\" : \"application/json\");\n  if (/\\r|\\n/.test(contentType)) throw badRequest(\"Invalid content type\");\n  let totalBytes = 0;\n  if (input.files?.length) {\n    if ([\"GET\", \"HEAD\"].includes(operation.method)) throw badRequest(\"Read requests cannot upload files\");\n    const form = new FormData();\n    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\");\n    for (const [key, value] of Object.entries((input.body ?? {}) as Record<string, unknown>)) {\n      const text = typeof value === \"string\" ? value : JSON.stringify(value);\n      totalBytes += Buffer.byteLength(text);\n      form.append(key, text);\n    }\n    for (const file of input.files) {\n      const resolved = await io.readFile(file);\n      totalBytes += resolved.bytes.length;\n      if (totalBytes > RUNNER_API_MAX_BYTES) throw badRequest(\"API upload exceeds the transfer limit\");","sourceCodeStart":126,"sourceCodeEnd":162,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/native-runtime/runner-api-client.ts#L126-L162","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nawait executeRunnerApi(input, context, { apiUrl, token: maybeToken, ... });\n// after\nif (!maybeToken) throw new Error(\"Paperclip run authentication is unavailable\");\nawait executeRunnerApi(input, context, { apiUrl, token: maybeToken, ... });","handlingStrategy":"type-guard","validationCode":"if (!io.token || typeof io.token !== \"string\") {\n  throw new Error(\"RunnerApiIo.token must be a non-empty JWT before calling executeRunnerApi\");\n}","typeGuard":"function hasToken(io: RunnerApiIo): io is RunnerApiIo & { token: string } {\n  return typeof io.token === \"string\" && io.token.length > 0;\n}","tryCatchPattern":"try {\n  const res = await executeRunnerApi(input, context, io);\n} catch (err) {\n  if (err instanceof Error && err.message === \"Paperclip run authentication is unavailable\") {\n    // re-mint the JWT (fix JWT config) and retry once\n  } else throw err;\n}","preventionTips":["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."],"tags":["authentication","jwt","guard","native-runtime"],"backgroundTag":"authentication-required","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"}