paperclipai/paperclip · error · HttpError
RESPONSIBLE_USER_UNAVAILABLE
RESPONSIBLE_USER_UNAVAILABLE
Error message
Responsible user is unavailable for this agent key
What it means
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.
Source
Thrown at server/src/middleware/auth.ts:393
.where(eq(agents.id, key.agentId))
.then((rows) => rows[0] ?? null);
if (!agentRecord || agentRecord.status === "terminated" || agentRecord.status === "pending_approval") {
next();
return;
}
const responsibleUserId = normalizeOptionalString(key.responsibleUserId);
if (!responsibleUserId) {
await auditAgentKeyMissingResponsibleUser(db, {
companyId: key.companyId,
agentId: key.agentId,
keyId: key.id,
method: req.method,
url: req.originalUrl,
});
next(forbidden("Responsible user is unavailable for this agent key", {
code: "RESPONSIBLE_USER_UNAVAILABLE",
}));
return;
}
req.actor = {
type: "agent",
agentId: key.agentId,
companyId: key.companyId,
keyId: key.id,
keyScope: normalizeAgentApiKeyScope(key.scopeConfig),
onBehalfOfUserId: responsibleUserId,
onBehalfOfMemberships: await loadResponsibleUserMemberships(db, {
companyId: key.companyId,
userId: responsibleUserId,
}),
runId: runIdHeader || undefined,
source: "agent_key",
};View on GitHub (pinned to 67001ec6eb)
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.
Example fix
-- before -- key row exists with responsibleUserId NULL -- after UPDATE agent_api_keys SET "responsibleUserId" = '<valid-user-id>' WHERE id = '<keyId>' AND "companyId" = '<companyId>';
Defensive patterns
Strategy: validation
Validate before calling
// Before issuing an agent API key, enforce the invariant:
function assertKeyHasResponsibleUser(key: {
responsibleUserId: string | null;
companyId: string;
}): void {
if (!key.responsibleUserId) {
throw new Error(
`Refusing to create agent_api_keys row without responsibleUserId (company=${key.companyId})`,
);
}
} Type guard
function keyHasResponsibleUser(
k: { responsibleUserId?: string | null },
): k is { responsibleUserId: string } {
return typeof k.responsibleUserId === 'string' && k.responsibleUserId.trim().length > 0;
} Try / catch
// Client side: detect the 403 and surface a re-auth flow.
try {
await api.callWithKey(agentKey);
} catch (err) {
if (err instanceof ApiError && err.code === 'RESPONSIBLE_USER_UNAVAILABLE') {
// key is valid but unowned: prompt admin to assign a responsible user
notifyAdminAssignResponsibleUser(err.keyId);
return;
}
throw err;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Request failed: ${response.status}
- CLI auth challenge was cancelled.
- CLI auth challenge expired before approval.
- Environment variable ${envName} is empty or not set.
- Challenge secret is required. Pass --token or --token-env.
AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12).
Data as JSON: /api/errors/3eb05b3f9fc50727.
Report an issue: GitHub.