{"record":{"id":"83c6dcbaa3d1e32b","repo":"paperclipai/paperclip","slug":"paperclip-runner-tool-not-bound","errorCode":"paperclip_runner_tool_not_bound","errorMessage":"paperclip_runner_tool_not_bound","messagePattern":"paperclip_runner_tool_not_bound","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/services/native-runtime/paperclip-runner-tool-authority.ts","lineNumber":238,"sourceCode":"      }\n      case \"get_approval_context\": {\n        const approval = await this.#approval(requiredString(input.approvalId));\n        const tasks = await this.db.select({ issue: issues }).from(issueApprovals)\n          .innerJoin(issues, eq(issues.id, issueApprovals.issueId))\n          .where(and(\n            eq(issueApprovals.approvalId, approval.id),\n            eq(issueApprovals.companyId, this.binding.companyId),\n            eq(issues.companyId, this.binding.companyId),\n          ));\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();","sourceCodeStart":220,"sourceCodeEnd":256,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/native-runtime/paperclip-runner-tool-authority.ts#L220-L256","documentation":"After passing advertisement and mode checks, execute() dispatches the tool to a private handler via a switch over operationId. At paperclip-runner-tool-authority.ts:238 the default arm throws 'paperclip_runner_tool_not_bound' when the tool name is implemented and advertised but has no dispatch case wired to a handler. This is an internal wiring invariant: every entry in IMPLEMENTED_OPERATIONS (minus the ones handled earlier, like search_api/call_api and connection tools) must have a corresponding case in the switch.","triggerScenarios":"execute() reaches the switch with a call.tool that survived the IMPLEMENTED_OPERATIONS check but matches no case — e.g. an operation was added to IMPLEMENTED_OPERATIONS/CAPABILITY_SEMANTIC_TOOL_CATALOG without adding a `case` in execute(), or a code change renamed a case while the catalog still uses the old operationId.","commonSituations":"A developer added a new capability-semantic tool to the catalog and IMPLEMENTED_OPERATIONS but forgot the switch arm; a merge dropped a case; an operation was moved to a different dispatcher but still listed as implemented; version skew where catalog and dispatcher come from different builds.","solutions":["Add the missing `case \"<tool>\": return this.#handler(input);` arm in execute() for the unbound operation.","Remove the operation from IMPLEMENTED_OPERATIONS/CAPABILITY_SEMANTIC_TOOL_CATALOG if it should not be callable.","Add a test asserting every IMPLEMENTED_OPERATIONS entry has a matching switch case so the wiring cannot drift.","Rebuild/redeploy so catalog and dispatcher come from the same build if version skew caused it."],"exampleFix":"// before\ncase \"set_dependencies\": return this.#setDependencies(input);\ndefault: throw new Error(\"paperclip_runner_tool_not_bound\");\n// after\ncase \"set_dependencies\": return this.#setDependencies(input);\ncase \"new_operation\": return this.#newOperation(input); // bind every implemented operation\ndefault: throw new Error(\"paperclip_runner_tool_not_bound\");","handlingStrategy":"try-catch","validationCode":"// Build-time exhaustive check: every implemented operation must have a dispatch arm\nconst BOUND_TOOLS = new Set([\"search_api\", \"call_api\", \"get_task_context\", \"get_task_history\", \"search_tasks\", \"list_documents\", \"read_document\", \"write_document\", \"list_document_revisions\", \"list_agents\", \"get_agent\", \"list_approvals\", \"get_approval\", \"get_approval_context\", \"report_progress\", \"request_human_input\", \"create_task\", \"set_dependencies\"]);\nfor (const op of IMPLEMENTED_OPERATIONS) if (!BOUND_TOOLS.has(op)) throw new Error(`operation ${op} implemented but not bound in execute()`);","typeGuard":null,"tryCatchPattern":"try {\n  return await authority.execute(call);\n} catch (err) {\n  if (err instanceof Error && err.message === \"paperclip_runner_tool_not_bound\") {\n    logger.error({ tool: call.tool }, \"implemented operation missing dispatch arm\");\n    return { error: \"internal_tool_wiring\", hint: \"Report this — the tool exists in the catalog but has no handler.\" };\n  }\n  throw err;\n}","preventionTips":["Enforce exhaustiveness with a test mapping IMPLEMENTED_OPERATIONS to switch cases in execute().","Add the dispatch arm in the same PR that adds an operation to IMPLEMENTED_OPERATIONS.","Prefer a Record<operationId, handler> map over a switch so TypeScript's exhaustiveness checking flags missing handlers.","Run targeted runner-tool tests after any catalog or dispatcher change."],"tags":["unwired-handler","invariant-violation","tool-dispatch","code-defect"],"backgroundTag":"method-not-implemented","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"}