{"record":{"id":"3eb05b3f9fc50727","repo":"paperclipai/paperclip","slug":"responsible-user-unavailable","errorCode":"RESPONSIBLE_USER_UNAVAILABLE","errorMessage":"Responsible user is unavailable for this agent key","messagePattern":"Responsible user is unavailable for this agent key","errorType":"error_code","errorClass":"HttpError","httpStatus":403,"severity":"error","filePath":"server/src/middleware/auth.ts","lineNumber":393,"sourceCode":"      .where(eq(agents.id, key.agentId))\n      .then((rows) => rows[0] ?? null);\n\n    if (!agentRecord || agentRecord.status === \"terminated\" || agentRecord.status === \"pending_approval\") {\n      next();\n      return;\n    }\n\n    const responsibleUserId = normalizeOptionalString(key.responsibleUserId);\n    if (!responsibleUserId) {\n      await auditAgentKeyMissingResponsibleUser(db, {\n        companyId: key.companyId,\n        agentId: key.agentId,\n        keyId: key.id,\n        method: req.method,\n        url: req.originalUrl,\n      });\n      next(forbidden(\"Responsible user is unavailable for this agent key\", {\n        code: \"RESPONSIBLE_USER_UNAVAILABLE\",\n      }));\n      return;\n    }\n\n    req.actor = {\n      type: \"agent\",\n      agentId: key.agentId,\n      companyId: key.companyId,\n      keyId: key.id,\n      keyScope: normalizeAgentApiKeyScope(key.scopeConfig),\n      onBehalfOfUserId: responsibleUserId,\n      onBehalfOfMemberships: await loadResponsibleUserMemberships(db, {\n        companyId: key.companyId,\n        userId: responsibleUserId,\n      }),\n      runId: runIdHeader || undefined,\n      source: \"agent_key\",\n    };","sourceCodeStart":375,"sourceCodeEnd":411,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/server/src/middleware/auth.ts#L375-L411","documentation":"Returned as HTTP 403 (code RESPONSIBLE_USER_UNAVAILABLE) by the agent-API-key auth middleware after the key and agent are loaded successfully but key.responsibleUserId is null/empty. The middleware writes an audit record (auditAgentKeyMissingResponsibleUser) before denying, because agent actions must be attributable to a human owner (onBehalfOfUserId). This is an authz failure on a valid key, not an authn failure.","triggerScenarios":"An agent_api_keys row exists and hashes to the presented bearer token, and the agent is active, but responsibleUserId is null/empty. Causes: the responsible user was deleted (FK nullified), the key was seeded by a migration without backfilling responsibleUserId, or the key was created before the responsible-user requirement and never updated.","commonSituations":"Data migration that created agent keys without a responsible user; user-account deletion that left responsibleUserId dangling; test fixtures with a hand-inserted key that omits the column; an older key being reused after the on-behalf-of requirement shipped.","solutions":["Update the agent_api_keys row to set responsibleUserId to a current member of the same company (the audit log entry names companyId/agentId/keyId).","If the intended user was deleted, either restore them or assign a new owner, then re-issue or re-link the key.","If the key is vestigial, revoke it and issue a new one with a valid responsibleUserId.","Add a backfill migration that sets responsibleUserId on legacy keys and a NOT NULL + FK check going forward."],"exampleFix":"-- before\n-- key row exists with responsibleUserId NULL\n\n-- after\nUPDATE agent_api_keys\nSET \"responsibleUserId\" = '<valid-user-id>'\nWHERE id = '<keyId>' AND \"companyId\" = '<companyId>';","handlingStrategy":"validation","validationCode":"// Before issuing an agent API key, enforce the invariant:\nfunction assertKeyHasResponsibleUser(key: {\n  responsibleUserId: string | null;\n  companyId: string;\n}): void {\n  if (!key.responsibleUserId) {\n    throw new Error(\n      `Refusing to create agent_api_keys row without responsibleUserId (company=${key.companyId})`,\n    );\n  }\n}","typeGuard":"function keyHasResponsibleUser(\n  k: { responsibleUserId?: string | null },\n): k is { responsibleUserId: string } {\n  return typeof k.responsibleUserId === 'string' && k.responsibleUserId.trim().length > 0;\n}","tryCatchPattern":"// Client side: detect the 403 and surface a re-auth flow.\ntry {\n  await api.callWithKey(agentKey);\n} catch (err) {\n  if (err instanceof ApiError && err.code === 'RESPONSIBLE_USER_UNAVAILABLE') {\n    // key is valid but unowned: prompt admin to assign a responsible user\n    notifyAdminAssignResponsibleUser(err.keyId);\n    return;\n  }\n  throw err;\n}","preventionTips":["Backfill responsibleUserId on legacy agent_api_keys rows and add a NOT NULL + FK constraint.","On user deletion, reassign or revoke their agent keys rather than nulling the column.","Monitor the auditAgentKeyMissingResponsibleUser audit stream for regressions."],"tags":["auth","authz","agent-key","responsible-user","audit","http-403"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}