{"record":{"id":"cd4deff1c1929755","repo":"paperclipai/paperclip","slug":"paperclip-api-origin-is-unavailable","errorCode":null,"errorMessage":"Paperclip API origin is unavailable","messagePattern":"Paperclip API origin is unavailable","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/services/native-runtime/paperclip-runner-tool-authority.ts","lineNumber":247,"sourceCode":"          ));\n        return { approval, tasks: tasks.map((row) => row.issue) };\n      }\n      case \"report_progress\": return this.#reportProgress(input);\n      case \"request_human_input\": return this.#requestHumanInput(input,\n        (await captureRunIdentity(this.db, this.binding)).context?.id ?? null);\n      case \"create_task\": return this.#createTask(input,\n        (await captureRunIdentity(this.db, this.binding)).context?.id ?? null);\n      case \"set_dependencies\": return this.#setDependencies(input);\n      default: throw new Error(\"paperclip_runner_tool_not_bound\");\n    }\n  }\n\n  async #callApi(callId: string, value: unknown): Promise<unknown> {\n    const bound = await this.#boundContext();\n    const context = { ...this.binding, issueIdentifier: bound.issue.identifier, workMode: bound.issue.workMode };\n    const { input, operation } = validateRunnerApiCall(value, context);\n    const apiUrl = this.binding.apiUrl ?? process.env.PAPERCLIP_API_URL;\n    if (!apiUrl) throw new Error(\"Paperclip API origin is unavailable\");\n    const token = createLocalAgentJwt(this.binding.agentId, this.binding.companyId, bound.actor.adapterType, this.binding.runId, bound.run.responsibleUserId);\n    if (!token) throw new Error(\"Paperclip run authentication is unavailable\");\n    const execute = async () => {\n      const current = await this.#boundContext();\n      if (!runnerApiToolsEnabled(this.binding.companyId, this.binding.apiToolsEnabled)) throw new Error(\"paperclip_runner_tool_not_advertised\");\n      return executeRunnerApi(input, { ...context, workMode: current.issue.workMode }, {\n        apiUrl, token,\n        beforeDispatch: async () => {\n          const fresh = await this.#boundContext();\n          if (!runnerApiToolsEnabled(this.binding.companyId, this.binding.apiToolsEnabled)) throw new Error(\"paperclip_runner_tool_not_advertised\");\n          validateRunnerApiCall(input, { ...context, workMode: fresh.issue.workMode });\n        },\n        readFile: (file) => this.#readApiFile(file),\n        saveResponse: async (bytes, contentType) => {\n          const storage = this.binding.storage ?? getStorageService();\n          const saved = await storage.putFile({ companyId: this.binding.companyId, namespace: \"runner-api\", originalFilename: contentType.includes(\"json\") ? \"response.json\" : \"response.bin\", contentType, body: bytes });\n          const asset = await assetService(this.db).create(this.binding.companyId, { ...saved, createdByAgentId: this.binding.agentId });\n          const activity = await persistActivity(this.db, { companyId: this.binding.companyId, actorType: \"agent\", actorId: this.binding.agentId, agentId: this.binding.agentId, runId: this.binding.runId, issueId: this.binding.issueId, action: \"asset.created\", entityType: \"asset\", entityId: asset.id, details: { source: \"runner.call_api\", byteSize: saved.byteSize } });","sourceCodeStart":229,"sourceCodeEnd":265,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/native-runtime/paperclip-runner-tool-authority.ts#L229-L265","documentation":"#callApi executes a validated 'call_api' runner operation by dispatching an HTTP request to the Paperclip API itself. It resolves the origin from binding.apiUrl, falling back to process.env.PAPERCLIP_API_URL; at paperclip-runner-tool-authority.ts:247 it throws 'Paperclip API origin is unavailable' when both are absent, because the runner cannot know where to send the authenticated API request.","triggerScenarios":"The runner binding was created without apiUrl and the server process lacks the PAPERCLIP_API_URL environment variable, and the agent invokes the call_api tool (any runner API operation that routes through #callApi).","commonSituations":"PAPERCLIP_API_URL not set in the deployment environment (missing .env, compose file, or systemd unit); self-hosted install where binding construction does not pass apiUrl; workers spawned in a different process/context that does not inherit the env var; rename of the env var in a version upgrade.","solutions":["Set PAPERCLIP_API_URL (e.g. PAPERCLIP_API_URL=http://localhost:3100) in the server/worker environment and restart the process.","Pass apiUrl explicitly when constructing the runner binding so it does not depend on the environment.","Check deployment config (.env, docker-compose, k8s manifest) to confirm the variable reaches the process that executes the runner.","If on an upgraded version, verify the current env var name in doc/DEVELOPING.md or server config and migrate any old variable names."],"exampleFix":"// before (no origin configured)\n// binding has no apiUrl; process.env.PAPERCLIP_API_URL undefined\n// after\nPAPERCLIP_API_URL=http://localhost:3100 pnpm dev\n// or: const binding = { ..., apiUrl: process.env.PAPERCLIP_API_URL ?? \"http://localhost:3100\" };","handlingStrategy":"validation","validationCode":"const apiUrl = binding.apiUrl ?? process.env.PAPERCLIP_API_URL;\nif (!apiUrl) throw new Error(\"Set PAPERCLIP_API_URL (or pass binding.apiUrl) before enabling the call_api runner tool\");\nnew URL(apiUrl); // also validate format","typeGuard":"function hasApiOrigin(binding: { apiUrl?: string | null }, env: NodeJS.ProcessEnv): binding is { apiUrl: string } & Record<string, unknown> {\n  return Boolean(binding.apiUrl ?? env.PAPERCLIP_API_URL);\n}","tryCatchPattern":"try {\n  return await authority.execute({ tool: \"call_api\", callId, arguments });\n} catch (err) {\n  if (err instanceof Error && err.message === \"Paperclip API origin is unavailable\") {\n    return { error: \"api_origin_unconfigured\", hint: \"Set PAPERCLIP_API_URL in the server environment.\" };\n  }\n  throw err;\n}","preventionTips":["Set PAPERCLIP_API_URL in every environment (dev .env, compose, k8s) that runs the runner.","Pass apiUrl explicitly in the runner binding for worker processes that may not inherit server env.","Add a startup check that fails fast when call_api is enabled but no API origin is resolvable.","Include the env var name and expected format (origin URL) in deployment docs and health checks."],"tags":["configuration","missing-env-var","api-url","network"],"backgroundTag":"missing-env-var","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}