{"record":{"id":"8a1d48edd33be592","repo":"paperclipai/paperclip","slug":"native-interaction-governed-request-unresolved","errorCode":"native_interaction_governed_request_unresolved","errorMessage":"native_interaction_governed_request_unresolved","messagePattern":"native_interaction_governed_request_unresolved","errorType":"error_code","errorClass":"NativeInteractionBridgeError","httpStatus":null,"severity":"error","filePath":"server/src/services/native-runtime/native-interaction-bridge.ts","lineNumber":190,"sourceCode":"  const responses: NativeInteractionResponseEnvelope[] = [];\n\n  for (const interaction of interactions) {\n    if (!requestedIds.has(interaction.id)) continue;\n    if (interaction.companyId !== input.companyId || interaction.issueId !== input.issueId) {\n      throw new NativeInteractionBridgeError(\n        \"native_interaction_binding_mismatch\",\n        `Interaction ${interaction.id} is not bound to the native company and issue`,\n      );\n    }\n    if (interaction.kind === \"request_confirmation\" && interaction.payload.toolAction) {\n      const action = interaction.payload.toolAction;\n      if ([\"accepted\", \"rejected\"].includes(interaction.status) && (interaction.resolvedByAgentId || interaction.resolvedByRunId === input.runId)) {\n        throw new NativeInteractionBridgeError(\"native_interaction_self_approval\", \"Agents cannot resolve governed tool reviews\");\n      }\n      const [request] = await input.db.select().from(toolActionRequests).where(and(eq(toolActionRequests.id, action.actionRequestId), eq(toolActionRequests.companyId, input.companyId), eq(toolActionRequests.issueId, input.issueId), eq(toolActionRequests.interactionId, interaction.id), eq(toolActionRequests.invocationId, action.invocationId)));\n      const [invocation] = await input.db.select().from(toolInvocations).where(and(eq(toolInvocations.id, action.invocationId), eq(toolInvocations.companyId, input.companyId), eq(toolInvocations.issueId, input.issueId), eq(toolInvocations.agentId, input.agentId)));\n      if (!request || !invocation || request.requestedByAgentId !== input.agentId || request.canonicalArgumentsHash !== action.argumentsHash) {\n        throw new NativeInteractionBridgeError(\"native_interaction_governed_request_unresolved\", \"Tool review has no matching authoritative invocation\");\n      }\n      if ([\"expired\", \"cancelled\"].includes(request.status)) {\n        if (interaction.status !== request.status && !(interaction.status === \"accepted\" && interaction.result?.toolAction?.status === \"expired\")) throw new NativeInteractionBridgeError(\"native_interaction_governed_result_mismatch\", \"Tool review lifecycle does not match its request\");\n        responses.push({ interactionId: interaction.id, kind: interaction.kind, response: { status: interaction.status, result: structuredClone(interaction.result), executionStatus: request.status } });\n        continue;\n      }\n      if (!request.decidedByUserId || request.decidedByUserId !== interaction.resolvedByUserId || ![\"executed\", \"failed\", \"rejected\"].includes(request.status) || (request.status === \"rejected\" ? interaction.status !== \"rejected\" : interaction.status !== \"accepted\")) {\n        throw new NativeInteractionBridgeError(\"native_interaction_governed_request_unresolved\", \"Tool review must have a human decision and an authoritative terminal execution outcome\");\n      }\n      const expectedInvocationStatus = request.status === \"executed\" ? \"succeeded\" : request.status === \"rejected\" ? \"denied\" : \"failed\";\n      if (invocation.status !== expectedInvocationStatus || (request.status !== \"rejected\" && interaction.result?.toolAction?.status !== request.status)) throw new NativeInteractionBridgeError(\"native_interaction_governed_result_mismatch\", \"Tool review outcome does not match its invocation\");\n    }\n    const interactionResult = record(interaction.result);\n    const supersessionOutcome = interaction.status === \"expired\"\n      && [\"superseded_by_newer_request\", \"superseded_by_comment\", \"stale_target\"].includes(String(interactionResult.outcome));\n    if (supersessionOutcome) {\n      const duplicate = interactionResult.outcome === \"superseded_by_newer_request\";\n      const decision = resolveNativeAttentionStatus({","sourceCodeStart":172,"sourceCodeEnd":208,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/native-runtime/native-interaction-bridge.ts#L172-L208","documentation":"For a governed `request_confirmation` interaction, the bridge verifies the tool review against its authoritative database records: a `toolActionRequests` row and a `toolInvocations` row must exist, scoped to the same company, issue, interaction, invocation, and requesting agent, and the request's `canonicalArgumentsHash` must match the hash in the payload. If any of these checks fail — no request, no invocation, wrong requesting agent, or hash mismatch — it throws `native_interaction_governed_request_unresolved`.","triggerScenarios":"Calling `materializeNativeInteractionResponses` with a toolAction response where: (1) `action.actionRequestId` or `action.invocationId` references no existing row; (2) the request/invocation belongs to a different company, issue, interaction, or agent than `input.agentId`; (3) `action.argumentsHash` differs from `request.canonicalArgumentsHash` (arguments were mutated after the request was created).","commonSituations":"The client cached a stale interaction payload after the request was re-created with new canonical arguments; a cross-issue or cross-agent id was pasted into the response payload; the invocation was deleted during cleanup while the interaction remained; an agent forged or guessed an actionRequestId for another agent's invocation.","solutions":["Verify `actionRequestId`, `invocationId`, and `argumentsHash` in the response payload match the original tool_action_request exactly; re-read the interaction from the DB and rebuild the payload rather than hand-assembling it.","Confirm the interaction is bound to the same company/issue/agent that the bridge input carries; cross-scope ids fail the lookup by design.","If arguments changed, create a fresh tool action request (new canonical hash) and a fresh interaction instead of updating the old one.","Query `tool_action_requests` by interactionId first to confirm the authoritative record exists before attempting to materialize the response."],"exampleFix":"// before: hand-built payload with recomputed arguments hash\nconst response = { toolAction: { actionRequestId, invocationId, argumentsHash: hash(newArgs) } };\n\n// after: use the hash recorded on the authoritative request\nconst [request] = await db.select().from(toolActionRequests)\n  .where(eq(toolActionRequests.interactionId, interaction.id));\nconst response = { toolAction: { actionRequestId: request.id, invocationId: request.invocationId, argumentsHash: request.canonicalArgumentsHash } };","handlingStrategy":"validation","validationCode":"const [req] = await db.select().from(toolActionRequests)\n  .where(and(eq(toolActionRequests.interactionId, interaction.id), eq(toolActionRequests.companyId, companyId)));\nif (!req || req.canonicalArgumentsHash !== action.argumentsHash || req.requestedByAgentId !== agentId) {\n  throw new Error(\"Payload does not match the authoritative tool action request\");\n}","typeGuard":"function matchesAuthoritativeRequest(action, req) {\n  return Boolean(req) && req.requestedByAgentId === action.agentId && req.canonicalArgumentsHash === action.argumentsHash;\n}","tryCatchPattern":"try {\n  await materializeNativeInteractionResponses(input);\n} catch (err) {\n  if (err instanceof NativeInteractionBridgeError && err.code === \"native_interaction_governed_request_unresolved\") {\n    // rebuild payload from the DB row; do not blind-retry with the same ids\n  }\n  throw err;\n}","preventionTips":["Always build toolAction payloads by reading the tool_action_requests row, never from client memory.","Create a new request + interaction whenever arguments change; never mutate the hash.","Validate company/issue/agent scoping of ids before submission.","Watch for deleted invocations during cleanup leaving orphan interactions."],"tags":["governance","integrity","hash-mismatch","database"],"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"}