{"record":{"id":"b88ddfadfe005e57","repo":"paperclipai/paperclip","slug":"identity-context-unavailable","errorCode":"identity_context_unavailable","errorMessage":"Approved action identity is unavailable","messagePattern":"Approved action identity is unavailable","errorType":"http","errorClass":"ToolGatewayHttpError","httpStatus":409,"severity":"error","filePath":"server/src/services/tool-gateway.ts","lineNumber":5578,"sourceCode":"      .where(eq(toolActionRequests.id, claimed.id));\n    await reflectToolActionInteractionLifecycle({ actionRequestId: claimed.id, status: \"expired\" });\n    throw new ToolGatewayHttpError(\n      409,\n      \"The issue for this tool action is closed; the approval has expired\",\n      \"action_issue_closed\",\n      { actionRequestId: claimed.id, invocationId: invocation.id },\n    );\n  }\n\n  async function restoreApprovedActionIdentity(session: ToolGatewaySession, identityContextId: string | undefined) {\n    if (!identityContextId) return;\n    const [origin] = await db.select().from(runIdentityContexts).where(and(\n      eq(runIdentityContexts.id, identityContextId),\n      eq(runIdentityContexts.companyId, session.companyId),\n      eq(runIdentityContexts.runId, session.runId!),\n      eq(runIdentityContexts.status, \"accepted\"),\n    ));\n    if (!origin) throw new ToolGatewayHttpError(409, \"Approved action identity is unavailable\", \"identity_context_unavailable\");\n    session.identityContextId = origin.id;\n    session.responsibleUserId = origin.cause === \"company_default\" ? null : origin.responsibleUserId;\n  }\n\n  async function executeApprovedAgentInvocation(input: {\n    actionRequest: typeof toolActionRequests.$inferSelect;\n    invocation: typeof toolInvocations.$inferSelect;\n  }) {\n    const { actionRequest, invocation } = input;\n    if (!invocation.agentId || !invocation.issueId || isTestOriginInvocation(invocation)) {\n      throw new ToolGatewayHttpError(409, \"Tool action request is not an agent-origin action\", \"action_origin_invalid\");\n    }\n\n    const [claimed] = await db\n      .update(toolActionRequests)\n      .set({ status: \"executing\", updatedAt: new Date() })\n      .where(and(eq(toolActionRequests.id, actionRequest.id), eq(toolActionRequests.status, \"approved\")))\n      .returning();","sourceCodeStart":5560,"sourceCodeEnd":5596,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/tool-gateway.ts#L5560-L5596","documentation":"Before executing an approved action, the gateway restores the identity context that was accepted at approval time. It looks up run_identity_contexts by id, company, run, and status 'accepted'; if no such accepted row exists, the approved identity cannot be re-established and execution is refused with a 409.","triggerScenarios":"executeApprovedAgentInvocation path calls restoreApprovedActionIdentity with an identityContextId that is missing, belongs to another company/run, or whose status is not 'accepted' (pending, rejected, or reset).","commonSituations":"Identity context was reset/re-requested after approval (status changed), run was restarted with new identity contexts, stale signed payload replayed after company data moved, or the row was deleted by cleanup.","solutions":["Re-run the approval flow so a fresh accepted run_identity_contexts row is created, then approve again","Verify the identityContextId in the signed payload matches the current run (not an old run's context)","Check run_identity_contexts status: re-accept the pending identity context before approving"],"exampleFix":"// before: stale context id\nawait restoreApprovedActionIdentity(session, staleIdentityContextId);\n// after: re-request approval with current accepted context\nconst [origin] = await db.select().from(runIdentityContexts).where(eq(runIdentityContexts.runId, session.runId));\nif (origin?.status !== 'accepted') throw new Error('Re-approve: identity context not accepted');","handlingStrategy":"validation","validationCode":"const [ctx] = await db.select().from(runIdentityContexts).where(and(eq(runIdentityContexts.id, identityContextId), eq(runIdentityContexts.status, 'accepted')));\nif (!ctx) throw new Error('Identity context not accepted; re-run approval');","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never reuse signed payloads across runs or identity resets","Re-request approval whenever the identity context changes status","Avoid deleting run_identity_contexts rows while actions are pending"],"tags":["identity","approval","state"],"backgroundTag":"record-not-found","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"}