{"record":{"id":"2dea43a011523eaf","repo":"nexu-io/open-design","slug":"connector-not-connected-2dea43","errorCode":"CONNECTOR_NOT_CONNECTED","errorMessage":"connector is not connected","messagePattern":"connector is not connected","errorType":"exception","errorClass":"ConnectorServiceError","httpStatus":403,"severity":"error","filePath":"apps/daemon/src/connectors/service.ts","lineNumber":771,"sourceCode":"    }\n  }\n\n  async execute(request: ConnectorExecuteRequest, context: ConnectorExecutionContext): Promise<ConnectorExecuteResponse> {\n    const fastDefinition = this.listFastDefinitions().find((candidate) => (\n      candidate.id === request.connectorId &&\n      candidate.allowedToolNames.includes(request.toolName) &&\n      candidate.tools.some((tool) => tool.name === request.toolName)\n    ));\n    const definition = fastDefinition ?? await this.getHydratedDefinition(request.connectorId, context.signal);\n    if (!definition) {\n      throw new ConnectorServiceError('CONNECTOR_NOT_FOUND', 'connector not found', 404);\n    }\n    const connector = this.toDetail(definition);\n    if (connector.status === 'disabled') {\n      throw new ConnectorServiceError('CONNECTOR_DISABLED', 'connector is disabled', 403);\n    }\n    if (connector.status !== 'connected') {\n      throw new ConnectorServiceError('CONNECTOR_NOT_CONNECTED', 'connector is not connected', 403, {\n        connectorId: request.connectorId,\n        status: connector.status,\n      });\n    }\n    if (request.expectedAccountLabel !== undefined && connector.accountLabel !== request.expectedAccountLabel) {\n      throw new ConnectorServiceError('CONNECTOR_NOT_CONNECTED', 'connector account changed since refresh approval', 409, {\n        connectorId: request.connectorId,\n        expectedAccountLabel: request.expectedAccountLabel,\n        currentAccountLabel: connector.accountLabel ?? null,\n      });\n    }\n    if (!definition.allowedToolNames.includes(request.toolName)) {\n      throw new ConnectorServiceError('CONNECTOR_TOOL_NOT_FOUND', 'connector tool is not allowed', 404, {\n        connectorId: request.connectorId,\n        toolName: request.toolName,\n      });\n    }\n    const tool = definition.tools.find((candidate) => candidate.name === request.toolName);","sourceCodeStart":753,"sourceCodeEnd":789,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/connectors/service.ts#L753-L789","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: firing a tool call regardless of connector state\nawait connectorService.execute({ connectorId, toolName, input }, ctx);\n\n// after: gate on live status and re-establish the connection first\nconst detail = connectorService.getConnector(connectorId);\nif (detail.status !== 'connected') {\n  await connectorService.connect(connectorId, { ... });\n}\nawait connectorService.execute({ connectorId, toolName, input }, ctx);","handlingStrategy":"validation","validationCode":"const detail = connectorService.getConnector(request.connectorId);\nif (!detail || detail.status !== 'connected') {\n  throw new Error(`connector ${request.connectorId} not connected (status=${detail?.status ?? 'missing'})`);\n}\nawait connectorService.execute(request, context);","typeGuard":"function isConnectorReady(detail: ConnectorDetail | undefined): detail is ConnectorDetail & { status: 'connected' } {\n  return !!detail && detail.status === 'connected';\n}","tryCatchPattern":"try { await connectorService.execute(request, context); }\ncatch (e) {\n  if (e instanceof ConnectorServiceError && e.code === 'CONNECTOR_NOT_CONNECTED' && e.status === 403) {\n    // prompt reconnect using e.details.status\n  } else throw e;\n}","preventionTips":["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."],"tags":["connector","auth","http-403","composio"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}