nexu-io/open-design · error · ConnectorServiceError

CONNECTOR_EXECUTION_FAILED

CONNECTOR_EXECUTION_FAILED

Error message

callbackUrl is required for Composio connectors

What it means

Thrown by ConnectorService.connect() when the resolved definition is Composio-backed (definition.authentication === 'composio'), no credentials were supplied (options.credentials === undefined), and options.callbackUrl is missing. The Composio OAuth redirect flow needs a callback URL to send the user back; without it the daemon cannot start the connection. ConnectorServiceError with code CONNECTOR_EXECUTION_FAILED and HTTP 400.

Source

Thrown at apps/daemon/src/connectors/service.ts:691

        return;
      }
      results[connectorId] = await composioConnectorProvider.prepareAuthConfig(definition, signal);
    }));

    return { results };
  }

  async connect(connectorId: string, options: { accountLabel?: string; credentials?: ConnectorCredentialMaterial; callbackUrl?: string; signal?: AbortSignal } = {}): Promise<ConnectorConnectResult> {
    const definition = this.getFastDefinition(connectorId) ?? await this.getDefinition(connectorId, options.signal);
    if (!definition) {
      throw new ConnectorServiceError('CONNECTOR_NOT_FOUND', 'connector not found', 404);
    }

    let auth: ComposioConnectionStart | undefined;
    let detailDefinition = definition;
    if (definition.authentication === 'composio' && options.credentials === undefined) {
      if (!options.callbackUrl) {
        throw new ConnectorServiceError('CONNECTOR_EXECUTION_FAILED', 'callbackUrl is required for Composio connectors', 400, { connectorId });
      }
      auth = await composioConnectorProvider.connect(definition, options.callbackUrl, options.signal);
      if (auth.kind === 'redirect_required' || auth.kind === 'pending') {
        return { connector: this.toDetail(detailDefinition), auth: publicComposioAuthStart(auth) };
      }
      if (auth.credentials !== undefined) {
        options = { ...options, ...(auth.accountLabel === undefined ? {} : { accountLabel: auth.accountLabel }), credentials: auth.credentials };
      }
    }

    const status = this.statusService.connect(detailDefinition, options.accountLabel, options.credentials);
    if (status.status === 'disabled') {
      throw new ConnectorServiceError('CONNECTOR_DISABLED', 'connector is disabled', 403);
    }
    return { connector: this.toDetail(detailDefinition), ...(auth === undefined ? {} : { auth: publicComposioAuthStart(auth) }) };
  }

  async disconnect(connectorId: string): Promise<ConnectorDetail> {

View on GitHub (pinned to 5be4028344)

Solutions

  1. Supply options.callbackUrl (a publicly/headlessly reachable URL that completes the OAuth round trip) when calling connect() for a Composio connector.
  2. If you already hold credentials, pass options.credentials to skip the OAuth redirect path entirely.
  3. For CLI/headless use, prefer pre-obtained credentials or an out-of-band OAuth flow rather than connect().

Example fix

// before
await connectorService.connect('composio-github', {}); // no callbackUrl, no credentials
// throws "callbackUrl is required for Composio connectors"

// after
await connectorService.connect('composio-github', {
  callbackUrl: 'https://app.example.com/api/connectors/callback',
});
Defensive patterns

Strategy: validation

Validate before calling

async function connectComposio(connectorService: ConnectorService, id: string, opts: { callbackUrl?: string; credentials?: Record<string, unknown> }) {
  const detail = await connectorService.getConnector(id);
  const isComposio = detail.auth?.flow === 'composio'; // or inspect the definition's authentication
  if (isComposio && !opts.credentials && !opts.callbackUrl) {
    throw new Error('callbackUrl is required to start a Composio OAuth flow');
  }
  return connectorService.connect(id, opts);
}

Type guard

function hasUsableComposioConnectOptions(opts: { callbackUrl?: string; credentials?: unknown }): boolean {
  return opts.credentials !== undefined || typeof opts.callbackUrl === 'string' && opts.callbackUrl.length > 0;
}

// if (!hasUsableComposioConnectOptions(opts)) throw new Error('provide callbackUrl or credentials');

Try / catch

try {
  return await connectorService.connect(id, opts);
} catch (error) {
  if (error instanceof ConnectorServiceError
      && error.code === 'CONNECTOR_EXECUTION_FAILED'
      && /callbackUrl is required/.test(error.message)) {
    return { ok: false, reason: 'missing_callback_url' };
  }
  throw error;
}

Prevention

When it happens

Trigger: Initiating a Composio OAuth connection without supplying credentials and without a callbackUrl, e.g. a CLI/agent call that calls connect() for an OAuth connector but has no HTTP endpoint to receive the redirect.

Common situations: CLI or headless agent tries to start an OAuth flow it cannot complete; the web route forgot to forward its callback URL; migration to a new callback host left the URL empty.

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/7985a3f54ffed0fe. Report an issue: GitHub.