nexu-io/open-design · error · ConnectorServiceError
CONNECTOR_NOT_CONNECTED
CONNECTOR_NOT_CONNECTED
Error message
connector is not connected
What it means
Thrown by ConnectorService.execute() when a tool call targets a connector whose live status (from statusService via toDetail) is anything other than 'connected' (e.g. 'disconnected', 'pending', 'expired', 'error'). It is a 403 gate checked before the tool is even looked up, so the call never reaches the provider. The thrown error carries details.connectorId and details.status so the caller can see which non-connected state blocked it.
Source
Thrown at apps/daemon/src/connectors/service.ts:771
}
}
async execute(request: ConnectorExecuteRequest, context: ConnectorExecutionContext): Promise<ConnectorExecuteResponse> {
const fastDefinition = this.listFastDefinitions().find((candidate) => (
candidate.id === request.connectorId &&
candidate.allowedToolNames.includes(request.toolName) &&
candidate.tools.some((tool) => tool.name === request.toolName)
));
const definition = fastDefinition ?? await this.getHydratedDefinition(request.connectorId, context.signal);
if (!definition) {
throw new ConnectorServiceError('CONNECTOR_NOT_FOUND', 'connector not found', 404);
}
const connector = this.toDetail(definition);
if (connector.status === 'disabled') {
throw new ConnectorServiceError('CONNECTOR_DISABLED', 'connector is disabled', 403);
}
if (connector.status !== 'connected') {
throw new ConnectorServiceError('CONNECTOR_NOT_CONNECTED', 'connector is not connected', 403, {
connectorId: request.connectorId,
status: connector.status,
});
}
if (request.expectedAccountLabel !== undefined && connector.accountLabel !== request.expectedAccountLabel) {
throw new ConnectorServiceError('CONNECTOR_NOT_CONNECTED', 'connector account changed since refresh approval', 409, {
connectorId: request.connectorId,
expectedAccountLabel: request.expectedAccountLabel,
currentAccountLabel: connector.accountLabel ?? null,
});
}
if (!definition.allowedToolNames.includes(request.toolName)) {
throw new ConnectorServiceError('CONNECTOR_TOOL_NOT_FOUND', 'connector tool is not allowed', 404, {
connectorId: request.connectorId,
toolName: request.toolName,
});
}
const tool = definition.tools.find((candidate) => candidate.name === request.toolName);View on GitHub (pinned to 5be4028344)
Solutions
- Reconnect the connector: call connect()/completeComposioConnection() (or use the UI / od connector connect) and wait for status 'connected' before retrying the tool.
- If the account genuinely should be swapped, point the request at a different connectorId that is already connected, or disconnect first and reconnect with the new credentials.
- Inspect details.status from the ConnectorServiceError to distinguish 'expired' (token refresh path) from 'disconnected' (user action) before deciding to prompt for re-auth.
Example fix
// before: firing a tool call regardless of connector state
await connectorService.execute({ connectorId, toolName, input }, ctx);
// after: gate on live status and re-establish the connection first
const detail = connectorService.getConnector(connectorId);
if (detail.status !== 'connected') {
await connectorService.connect(connectorId, { ... });
}
await connectorService.execute({ connectorId, toolName, input }, ctx); Defensive patterns
Strategy: validation
Validate before calling
const detail = connectorService.getConnector(request.connectorId);
if (!detail || detail.status !== 'connected') {
throw new Error(`connector ${request.connectorId} not connected (status=${detail?.status ?? 'missing'})`);
}
await connectorService.execute(request, context); Type guard
function isConnectorReady(detail: ConnectorDetail | undefined): detail is ConnectorDetail & { status: 'connected' } {
return !!detail && detail.status === 'connected';
} Try / catch
try { await connectorService.execute(request, context); }
catch (e) {
if (e instanceof ConnectorServiceError && e.code === 'CONNECTOR_NOT_CONNECTED' && e.status === 403) {
// prompt reconnect using e.details.status
} else throw e;
} Prevention
- Check connector.status via getConnector() before issuing execute().
- Subscribe to connector status changes (connected/disconnected/expired) and re-route queued calls.
- Treat 401/403 from the provider as a signal to invalidate status before the next call.
When it happens
Trigger: Calling POST /api/connectors/execute (or the internal execute path) for a connectorId whose toDetail(definition).status resolves to a value != 'connected' — for example after disconnect(), after markAuthenticationExpired flipped it to 'expired', or before a Composio OAuth completeConnection() has finished.
Common situations: The agent retried a connector tool call after the user disconnected the account in the UI; the OAuth token expired mid-run and a prior provider call set status to 'expired' via markAuthenticationExpired; a pending Composio connection was never completed but the tool was already queued.
Related errors
- CONNECTOR_OUTPUT_TOO_LARGE
- CONNECTOR_NOT_FOUND
- CONNECTOR_EXECUTION_FAILED
- CONNECTOR_SAFETY_DENIED
- WORKSPACE_AUTHORITY_UNAVAILABLE
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/2dea43a011523eaf.
Report an issue: GitHub.