{"record":{"id":"b029c3bafc08d44f","repo":"paperclipai/paperclip","slug":"agent-identity-is-required","errorCode":null,"errorMessage":"Agent identity is required","messagePattern":"Agent identity is required","errorType":"http","errorClass":null,"httpStatus":401,"severity":"error","filePath":"server/src/routes/plugins.ts","lineNumber":976,"sourceCode":"   *\n   * Query params:\n   * - `pluginId` (optional): Filter to tools from a specific plugin\n   *\n   * Response: `AgentToolDescriptor[]`\n   * Errors: 501 if tool dispatcher is not configured\n   */\n  router.get(\"/plugins/tools\", async (req, res) => {\n    assertBoardOrAgent(req);\n\n    if (!toolDeps) {\n      res.status(501).json({ error: \"Plugin tool dispatch is not enabled\" });\n      return;\n    }\n\n    const pluginId = req.query.pluginId as string | undefined;\n    if (req.actor.type === \"agent\" && toolGatewayDeps) {\n      if (!req.actor.companyId || !req.actor.agentId) {\n        res.status(401).json({ error: \"Agent identity is required\" });\n        return;\n      }\n      const tools = await toolGatewayDeps.toolGateway.listPluginToolsForAgent({\n        companyId: req.actor.companyId,\n        agentId: req.actor.agentId,\n      });\n      res.json(pluginId ? tools.filter((tool) => tool.pluginId === pluginId || tool.name.startsWith(`${pluginId}:`)) : tools);\n      return;\n    }\n\n    const filter = pluginId ? { pluginId } : undefined;\n    const tools = toolDeps.toolDispatcher.listToolsForAgent(filter);\n    res.json(tools);\n  });\n\n  /**\n   * POST /api/plugins/tools/execute\n   *","sourceCodeStart":958,"sourceCodeEnd":994,"githubUrl":"https://github.com/paperclipai/paperclip/blob/a7e689b3c35347b529cb9f54c9b9a8575a3dcab6/server/src/routes/plugins.ts#L958-L994","documentation":"Returned as HTTP 401 by GET /api/plugins/tools (server/src/routes/plugins.ts:961) when the request authenticated as an agent actor (bearer agent API key) and the tool gateway is configured, but the actor lacks companyId or agentId. The tool gateway scopes plugin tool visibility per company+agent, so an agent identity without both bindings cannot be authorized.","triggerScenarios":"Calling GET /api/plugins/tools with an agent API key whose actor resolution produced no companyId/agentId — e.g. the key is not linked to an agent row, the linked agent or company was deleted while the key still validates, or a hand-crafted key context missing claims. Only affects req.actor.type === 'agent'; board actors skip this branch.","commonSituations":"Using an orphaned agent_api_keys entry after the agent was removed; key issued before company enrollment completed; copying keys between environments where the agent records differ.","solutions":["Re-issue the agent API key through the proper agent enrollment flow so it is bound to a live company and agent","Verify the agent and its company still exist and the agent_api_keys row references them","Confirm the request uses the Authorization: Bearer <agent-key> header (not a board session) for agent-context calls"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Before executing agent-scoped calls, confirm the key resolves to full identity\nconst me = await api.getAgentIdentity(); // returns actor context\nif (!me.companyId || !me.agentId) {\n  throw new Error(\"Agent key is not bound to a company/agent — re-enroll the key\");\n}","typeGuard":"type Actor = { type: string; companyId?: string; agentId?: string };\nconst hasAgentIdentity = (\n  a: Actor,\n): a is Actor & { companyId: string; agentId: string } =>\n  a.type === \"agent\" && typeof a.companyId === \"string\" && typeof a.agentId === \"string\";","tryCatchPattern":null,"preventionTips":["Issue agent API keys only through agent enrollment so they always carry companyId/agentId bindings","Delete or rotate keys when their agent or company is removed"],"tags":["plugins","plugin-tools","authentication","agent-keys","http-401"],"backgroundTag":"auth-identity-missing","analyzedSha":"a7e689b3c35347b529cb9f54c9b9a8575a3dcab6","analyzedAt":"2026-08-18T22:49:45.177Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}