{"record":{"id":"79d51df4eca2f6e3","repo":"paperclipai/paperclip","slug":"skill-tool-authentication-is-unavailable","errorCode":null,"errorMessage":"Skill tool authentication is unavailable","messagePattern":"Skill tool authentication is unavailable","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/src/services/native-runtime/paperclip-runner-tool-authority.ts","lineNumber":349,"sourceCode":"    if (call.tool === REUSE_CHAT_ATTACHMENT_TOOL_NAME) {\n      return this.#reuseChatAttachment(input);\n    }\n    const descriptor = CAPABILITY_SEMANTIC_TOOL_CATALOG.find(\n      (candidate) => candidate.operationId === call.tool,\n    );\n    if (\n      !descriptor ||\n      !descriptor.allowedModes.includes(\n        context.issue.workMode as \"standard\" | \"planning\" | \"ask\",\n      )\n    ) {\n      throw new Error(\"paperclip_runner_tool_mode_denied\");\n    }\n    switch (call.tool) {\n      case \"create_skill\": {\n        const apiUrl = this.binding.apiUrl ?? process.env.PAPERCLIP_API_URL;\n        const token = createLocalAgentJwt(this.binding.agentId, this.binding.companyId, context.actor.adapterType, this.binding.runId, context.run.responsibleUserId);\n        if (!apiUrl || !token) throw new Error(\"Skill tool authentication is unavailable\");\n        return callCreateSkillTool({ arguments: input, apiUrl, token, companyId: this.binding.companyId });\n      }\n      case \"create_project\":\n      case \"list_project_repositories\":\n      case \"list_projects\": {\n        const apiUrl = this.binding.apiUrl ?? process.env.PAPERCLIP_API_URL;\n        const token = createLocalAgentJwt(this.binding.agentId, this.binding.companyId, context.actor.adapterType, this.binding.runId, context.run.responsibleUserId);\n        if (!apiUrl || !token) throw new Error(\"Project tool authentication is unavailable\");\n        return callProjectTool({ name: call.tool, arguments: input, apiUrl, token,\n          companyId: this.binding.companyId, issueId: this.binding.issueId, agentId: this.binding.agentId,\n          conversation: Boolean(context.issue.conversationAgentId) });\n      }\n      case \"search_api\": return searchRunnerApi(call.arguments);\n      case \"call_api\": {\n        // PRP reserves operationId/callId for semantic result identity. The\n        // HTTP operation is metadata, including in previously saved receipts;\n        // exposing it as operationId makes the runner reject a valid response.\n        const { operationId, ...response } = record(await this.#callApi(call.callId, call.arguments));","sourceCodeStart":331,"sourceCodeEnd":367,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/native-runtime/paperclip-runner-tool-authority.ts#L331-L367","documentation":"The create_skill tool call-back to the Paperclip API needs both an API URL and an agent JWT. The URL comes from binding.apiUrl ?? process.env.PAPERCLIP_API_URL and the token from createLocalAgentJwt(...). This error means at least one of those was missing/empty, so the skill-creation request cannot be authenticated.","triggerScenarios":"execute() handles call.tool === 'create_skill', the mode check passes, and either apiUrl is falsy (binding.apiUrl unset AND PAPERCLIP_API_URL env var missing/empty) or createLocalAgentJwt returned an empty token (missing agent/company/run/actor inputs).","commonSituations":"Runner launched outside the server process without PAPERCLIP_API_URL set; deployment config dropped the env var; binding.apiUrl not injected by the runner host; createLocalAgentJwt inputs (adapterType, responsibleUserId) missing from context.","solutions":["Set PAPERCLIP_API_URL (e.g., http://localhost:3100) in the runner's environment.","Pass apiUrl explicitly in the runner binding so it doesn't depend on the env var.","Verify the server constructs the binding with agentId/companyId/runId so createLocalAgentJwt can mint a token.","Check that context.actor.adapterType and context.run.responsibleUserId are populated before tool execution."],"exampleFix":"// before\nPAPERCLIP_API_URL= node dist/server.js  // unset\n// after\nPAPERCLIP_API_URL=http://localhost:3100 node dist/server.js","handlingStrategy":"validation","validationCode":"function assertSkillToolAuth(binding) {\n  const apiUrl = binding.apiUrl ?? process.env.PAPERCLIP_API_URL;\n  if (!apiUrl) throw new Error(\"PAPERCLIP_API_URL must be set for skill tools\");\n  return apiUrl;\n}","typeGuard":"const hasSkillAuth = (binding) => Boolean(binding.apiUrl ?? process.env.PAPERCLIP_API_URL);","tryCatchPattern":"try {\n  return await authority.execute(call);\n} catch (e) {\n  if (e.message === \"Skill tool authentication is unavailable\") {\n    return respondSkipped(\"Skill tools require PAPERCLIP_API_URL; configure the runner environment and retry.\");\n  }\n  throw e;\n}","preventionTips":["Set PAPERCLIP_API_URL in every environment that launches native runners.","Prefer passing apiUrl explicitly via the runner binding over relying on env vars.","Health-check tool authentication (URL present, JWT mintable) at runner startup.","Keep run rows populated with responsibleUserId so JWT creation succeeds."],"tags":["authentication","missing-env-var","runner-tools","config"],"backgroundTag":"missing-env-var","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}