paperclipai/paperclip · error · ToolGatewayHttpError

user_authorization_required

user_authorization_required

Error message

User authorization is required

What it means

The connection's credential policy resolved to 'user_authorization_required', meaning a human user's personal authorization is needed before the tool can run, and no user grant exists. If an actingUserId is present the gateway also creates a user-authorization interaction (asking the human to connect), then throws a 409.

Source

Thrown at server/src/services/tool-gateway.ts:3406

          eq(companyMemberships.principalType, "user"),
          eq(companyMemberships.principalId, userGrant.subjectUserId),
          eq(companyMemberships.status, "active"),
        )).limit(1);
        if (!membership) {
          throw new ToolGatewayHttpError(403, "The delegated personal grant owner is not an active company member", "grant_owner_membership_inactive", {
            connectionId: connection.id,
            grantId: userGrant.id,
          });
        }
      }
    }
    const resolution = userGrant
      ? "user"
      : resolveCredentialGrantKind(connection.credentialPolicy, actingUserId, false);
    if (resolution === "user" && userGrant) return userGrant;
    if (resolution === "user_authorization_required") {
      if (actingUserId) await createUserAuthorizationInteraction(session, connection, actingUserId);
      throw new ToolGatewayHttpError(409, "User authorization is required", "user_authorization_required", {
        connectionId: connection.id,
        actingUserId,
      });
    }
    return findOrganizationGrant();
  }

  async function resolveConnectedRemoteTool(session: ToolGatewaySession, tool: ToolGatewayDescriptor) {
    if (tool.providerType !== "mcp_remote_http" || !tool.connectionId || !tool.catalogEntryId) {
      throw new ToolGatewayHttpError(404, `Tool "${tool.name}" not found`, "tool_not_found");
    }
    const [entry] = await db
      .select()
      .from(toolCatalogEntries)
      .where(and(
        eq(toolCatalogEntries.id, tool.catalogEntryId),
        eq(toolCatalogEntries.companyId, session.companyId),
      ))

View on GitHub (pinned to 01ad858492)

Solutions

  1. Complete the user-authorization interaction: have the acting user connect/authorize the connection in the UI
  2. Create a user-kind connection grant for the acting user via the connections authorization flow
  3. Change the connection credentialPolicy to an organization credential if personal grants are not desired
Defensive patterns

Strategy: try-catch

Validate before calling

const grant = await findActiveUserGrant(connection.id, actingUserId);
if (!grant && resolveCredentialGrantKind(connection.credentialPolicy, actingUserId, false) === 'user_authorization_required') {
  await createUserAuthorizationInteraction(session, connection, actingUserId); // surface connect prompt first
}

Try / catch

try { await runTool(...) } catch (e) { if (e.code === 'user_authorization_required') { await promptUserToAuthorizeConnection(e.details.connectionId); } else throw e; }

Prevention

When it happens

Trigger: A tool on a connection whose credentialPolicy requires per-user authorization is invoked while resolveCredentialGrantKind returns 'user_authorization_required' — no active user-kind grant for the acting user and no delegation supplying userGrant.

Common situations: New user invoking an agent tool before linking their own OAuth account; token revoked/expired leaving no active grant; policy tightened from org-credential to user-credential after deployment.

Related errors


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/36755492157af782. Report an issue: GitHub.