ComposioHQ/composio · error · ComposioAclOnlyForSharedError

${error.message (upstream BadRequestError: ACL only allowed

Error message

${error.message (upstream BadRequestError: ACL only allowed for shared connections)}

What it means

Wrapped ComposioAclOnlyForSharedError: the backend returned BadRequestError containing the 'ACL only allowed for shared connections' fragment. The connection request set acl/permissions on a non-shared connection, which the API rejects.

Source

Thrown at ts/packages/core/src/models/ConnectedAccounts.ts:446

        response.connected_account_id,
        ConnectedAccountStatuses.INITIATED,
        response.redirect_url
      );
      return connectionRequest;
    } catch (error) {
      // Caller-initiated cancellation must surface as the typed error,
      // not get remapped to ComposioFailedToCreateConnectedAccountLink below.
      if (error instanceof ComposioRequestCancelledError) {
        throw error;
      }
      // The server rejects ACL on PRIVATE connections — surface that as a
      // typed error so callers can `instanceof` instead of grepping messages.
      if (
        error instanceof BadRequestError &&
        typeof error.message === 'string' &&
        error.message.includes(ACL_ONLY_FOR_SHARED_ERROR_FRAGMENT)
      ) {
        throw new ComposioAclOnlyForSharedError(error.message, { cause: error });
      }
      throw new ComposioFailedToCreateConnectedAccountLink(
        'Failed to create connected account link',
        {
          cause: error,
        }
      );
    }
  }

  /**
   * Waits for a connection request to complete and become active.
   *
   * This method continuously polls the Composio API to check the status of a connection
   * until it either becomes active, enters a terminal error state, or times out.
   *
   * @param {string} connectedAccountId - The ID of the connected account to wait for
   * @param {number} [timeout=60000] - Maximum time to wait in milliseconds (default: 60 seconds)

View on GitHub (pinned to 64b1b85502)

Solutions

  1. Remove acl settings for personal (non-shared) connections
  2. Mark the connection as shared if ACLs are required
  3. Catch ComposioAclOnlyForSharedError and retry without the acl option

Example fix

// before
await c.connectedAccounts.link('github', { user:'u1', acl: teamAcl });
// after
await c.connectedAccounts.link('github', { user:'u1' }); // acl only on shared connections
Defensive patterns

Strategy: try-catch

Validate before calling

if (options.acl && !isSharedConnection) delete options.acl; // only send acl for shared connections

Type guard

const isAclOnlyForShared = (e: unknown): boolean => e instanceof ComposioAclOnlyForSharedError;

Try / catch

try { await ca.link(id, opts); } catch (e) { if (e instanceof ComposioAclOnlyForSharedError) { const { acl, ...rest } = opts; await ca.link(id, rest); } }

Prevention

When it happens

Trigger: Calling link() with an acl/permission option while the connection is not a shared (team-level) connection; the backend BadRequest is rethrown as a typed error so callers can instanceof-check it.

Common situations: Migrating from team shared-connection code to per-user connections but leaving ACL config in place; enabling ACL fields conditionally so they leak into personal connections.

Related errors


AI-assisted analysis of ComposioHQ/composio@64b1b85502 (2026-08-28). Data as JSON: /api/errors/b4f6a2234b41b0b7. Report an issue: GitHub.