{"record":{"id":"27e587293c85dcc0","repo":"paperclipai/paperclip","slug":"github-identity-unavailable","errorCode":"github_identity_unavailable","errorMessage":"No GitHub identity connected","messagePattern":"No GitHub identity connected","errorType":"http","errorClass":"ToolGatewayHttpError","httpStatus":409,"severity":"error","filePath":"server/src/services/tool-gateway.ts","lineNumber":2078,"sourceCode":"  async function findToolForSession(session: ToolGatewaySession, toolName: string): Promise<ToolGatewayDescriptor> {\n    const connectedTools = await connectedMcpToolsForCompany(session.companyId);\n    const hasOnDemandTargets = connectedTools.some(isOnDemandRemoteTool);\n    const virtualTools = hasOnDemandTargets ? VIRTUAL_TOOLS : [];\n    const tool = [...allTools(), ...connectedTools, ...virtualTools]\n      .filter((candidate) => session.agentId || (candidate.providerType !== \"paperclip_self\" && candidate.providerType !== \"paperclip_plugin\"))\n      .find((candidate) => candidate.name === toolName);\n    if (!tool) {\n      throw new ToolGatewayHttpError(404, `Tool \"${toolName}\" not found`, \"tool_not_found\", { tool: toolName });\n    }\n    if (session.identityContextId && session.agentId && tool.connectionId) {\n      const [connection] = await db.select().from(toolConnections).where(and(\n        eq(toolConnections.id, tool.connectionId), eq(toolConnections.companyId, session.companyId),\n      ));\n      if (connection?.config.sourceTemplateKey === \"github\" || connection?.transportConfig?.sourceTemplateKey === \"github\") {\n        let selected = await resolveManagedGitHubIdentitySelection(db, session.companyId, {\n          agentId: session.agentId, responsibleUserId: session.responsibleUserId, allowStandingDelegation: false,\n        });\n        if (!selected.grant) throw new ToolGatewayHttpError(409, selected.error ?? \"No GitHub identity connected\", \"github_identity_unavailable\");\n        const original = selected.grant;\n        // Acquire before policy evaluation or dispatch. An alternate connection\n        // gets its own catalog descriptor and policy checks; never replay a call.\n        for (let attempt = 0; attempt < 2; attempt += 1) {\n          const grant = selected.grant!;\n          const target = connectedTools.find((candidate) => candidate.connectionId === grant.connectionId\n            && candidate.upstreamToolName === tool.upstreamToolName && candidate.providerType === tool.providerType);\n          if (!target) throw new ToolGatewayHttpError(404, \"This GitHub tool is unavailable for the responsible person\", \"github_tool_unavailable\");\n          const [selectedConnection] = await db.select().from(toolConnections).where(and(\n            eq(toolConnections.id, grant.connectionId), eq(toolConnections.companyId, session.companyId),\n          ));\n          if (!selectedConnection) throw new ToolGatewayHttpError(409, \"GitHub connection is unavailable\", \"github_identity_unavailable\");\n          if (attempt === 0) await resolveConnectionGrant(session, selectedConnection);\n          try {\n            const headers = await resolveCredentialHeaders(session, selectedConnection, grant);\n            githubOperationCredentials.set(session, { grant, headers });\n            return target;\n          } catch (error) {","sourceCodeStart":2060,"sourceCodeEnd":2096,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/tool-gateway.ts#L2060-L2096","documentation":"The Paperclip tool gateway resolves a managed GitHub identity for the session before dispatching a call through a connection whose source template is \"github\". If resolveManagedGitHubIdentitySelection finds no identity grant for the company/responsible user (and standing delegation is disallowed), the gateway aborts with HTTP 409 and code github_identity_unavailable. It means the agent session has no usable GitHub identity to act as.","triggerScenarios":"An agent invokes a GitHub-backed tool via the gateway where toolConnections row has config.sourceTemplateKey (or transportConfig.sourceTemplateKey) === \"github\", and resolveManagedGitHubIdentitySelection returns no grant: no identity is connected for the responsible user, no agent-scoped grant exists, and allowStandingDelegation is false so delegation cannot substitute. selected.error is included in the message when present.","commonSituations":"Agent tries a GitHub tool before anyone connected GitHub in Apps/connections for that company; the responsible user changed to someone without a linked GitHub identity; a grant was revoked/deleted; org setup expects standing delegation but the call path passes allowStandingDelegation:false.","solutions":["Connect a managed GitHub identity for the company/responsible user via the Apps catalog GitHub connection flow before dispatching the tool call.","Verify the responsibleUserId on the session actually has a linked GitHub grant; re-link if it was revoked.","If delegation is appropriate, allow standing delegation in resolveManagedGitHubIdentitySelection or grant the agent its own identity.","Check the message body for the underlying selected.error detail to distinguish 'not connected' from 'connected but not selectable'.","Return a clear operator action in the UI: prompt the responsible user to connect GitHub under Apps."],"exampleFix":"// before\nconst res = await gateway.call(session, \"github_list_issues\", args); // throws 409\n// after\nconst sel = await resolveManagedGitHubIdentitySelection(db, session.companyId, { agentId: session.agentId, responsibleUserId: session.responsibleUserId, allowStandingDelegation: false });\nif (!sel.grant) throw new Error(`Connect GitHub first: ${sel.error}`);\nconst res = await gateway.call(session, \"github_list_issues\", args);","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":["github","oauth","tool-gateway","authorization"],"backgroundTag":"missing-credentials","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"}