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

  1. 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).
  2. If the intended user was deleted, either restore them or assign a new owner, then re-issue or re-link the key.
  3. If the key is vestigial, revoke it and issue a new one with a valid responsibleUserId.
  4. 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

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


AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12). Data as JSON: /api/errors/3eb05b3f9fc50727. Report an issue: GitHub.