paperclipai/paperclip · error · ToolGatewayHttpError

ambiguous_personal_grant

ambiguous_personal_grant

Error message

More than one delegated personal authorization matches this autonomous run

What it means

The tool gateway resolves a user-scope ('personal') credential grant for an autonomous run by querying connection grant delegations for the session's agent. When more than one active delegated personal grant matches the same connection and agent, the gateway cannot pick one deterministically and throws this 409 instead of guessing.

Source

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

    // accepted from agent input, so a run carrying it uses that owner's grant
    // directly. Delegation is reserved for genuinely ownerless unattended runs.
    let userGrant = connection.credentialPolicy === "shared" ? undefined : await findUserGrant();
    if (!userGrant && !actingUserId && autonomous && session.agentId && connection.credentialPolicy !== "shared") {
      const delegated = await db.select({ grant: connectionGrants }).from(connectionGrantDelegations).innerJoin(
        connectionGrants,
        and(
          eq(connectionGrants.id, connectionGrantDelegations.grantId),
          eq(connectionGrants.companyId, connectionGrantDelegations.companyId),
        ),
      ).where(and(
        eq(connectionGrantDelegations.companyId, connection.companyId),
        eq(connectionGrantDelegations.agentId, session.agentId),
        eq(connectionGrants.connectionId, connection.id),
        eq(connectionGrants.kind, "user"),
        eq(connectionGrants.status, "active"),
      ));
      if (delegated.length > 1) {
        throw new ToolGatewayHttpError(409, "More than one delegated personal authorization matches this autonomous run", "ambiguous_personal_grant", {
          connectionId: connection.id,
          agentId: session.agentId,
        });
      }
      userGrant = delegated[0]?.grant;
      if (userGrant?.subjectUserId) {
        const [membership] = await db.select({ id: companyMemberships.id }).from(companyMemberships).where(and(
          eq(companyMemberships.companyId, connection.companyId),
          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,
          });
        }

View on GitHub (pinned to 01ad858492)

Solutions

  1. Query connectionGrantDelegations for the agentId/connectionId and revoke or delete all but one active delegation
  2. Set the older duplicate delegation's status to revoked/inactive via the grants admin API or UI
  3. Re-run the tool call once a single active delegated personal grant remains

Example fix

// before: duplicate delegations
await db.delete(connectionGrantDelegations).where(and(eq(connectionGrantDelegations.agentId, agentId), eq(connectionGrantDelegations.grantId, staleGrantId)));
// after: keep exactly one active delegation
const keep = delegated[0];
await db.update(connectionGrantDelegations).set({ status: 'revoked' }).where(and(eq(connectionGrantDelegations.agentId, agentId), ne(connectionGrantDelegations.grantId, keep.grantId)));
Defensive patterns

Strategy: validation

Validate before calling

const delegated = await db.select().from(connectionGrantDelegations).where(and(eq(connectionGrantDelegations.agentId, agentId), eq(connectionGrantDelegations.grantId, grantId)));
if (delegated.filter(d => d.status === 'active').length > 1) throw new Error('De-duplicate delegations before running');

Prevention

When it happens

Trigger: An agent session with agentId set requests a tool bound to a connection with kind 'user' credential policy, and the DB contains >=2 rows in connection_grant_delegations joining to active user-kind connection_grants for that (connectionId, agentId) pair.

Common situations: Re-hiring or re-delegating the same personal connection to the same agent without revoking the previous delegation; a bug or manual DB edit duplicating delegations; company merges that copy grant rows.

Related errors


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