{"record":{"id":"220f9b38c7bf0c52","repo":"stablyai/orca","slug":"not-connected-method","errorCode":null,"errorMessage":"Not connected: ${method}","messagePattern":"Not connected: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"mobile/src/transport/rpc-client.ts","lineNumber":981,"sourceCode":"    registerPending: (id, onSettled) => pending.set(id, { resolve: onSettled, reject: onSettled }),\n    clearPending: (id) => pending.delete(id),\n    sendProbe: (id) => sendEncrypted({ id, deviceToken, method: 'status.get' }),\n    forceReconnect: closeAndSynthesize\n  })\n\n  openConnection()\n\n  return {\n    async sendRequest(\n      method: string,\n      params?: unknown,\n      options?: SendRequestOptions\n    ): Promise<RpcResponse> {\n      const budget = openRpcRequestBudget(options)\n      const waitStart = budget.startedAt\n      const wasConnected = state === 'connected'\n      if (options?.failWhenDisconnected && !wasConnected) {\n        throw new Error(`Not connected: ${method}`)\n      }\n      await waitForConnected(options?.timeoutMs)\n      if (!wasConnected) {\n        console.log('[net] sendRequest waited for connect', {\n          method,\n          waitedMs: Date.now() - waitStart\n        })\n      }\n\n      return new Promise((resolve, reject) => {\n        const id = nextId()\n        const timeoutMs = resolvePostConnectRequestTimeout(budget, REQUEST_TIMEOUT_MS)\n        const timeout = setTimeout(() => {\n          pending.delete(id)\n          console.log('[net] sendRequest TIMEOUT', {\n            method,\n            timeoutMs,\n            state","sourceCodeStart":963,"sourceCodeEnd":999,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/mobile/src/transport/rpc-client.ts#L963-L999","documentation":"Thrown by sendRequest in rpc-client.ts (line 980-982) when the caller passed options.failWhenDisconnected:true AND the connection state is not 'connected' at call time. This is the fail-fast opt-in: by default sendRequest parks in waitForConnected and waits for a reconnect (replaying the request after), but interactive PTY writes (terminal bytes, composer sends) set failWhenDisconnected because a stale byte replayed after a 30s reconnect corrupts the terminal. The thrown method name identifies which RPC was refused.","triggerScenarios":"client.sendRequest(method, params, { failWhenDisconnected: true }) called while state ∈ {'connecting','handshaking','disconnected','reconnecting','auth-failed'}. Most commonly: terminal write paths, composer 'send' loops, or any caller that sized its semantics against immediate delivery rather than eventual delivery.","commonSituations":"Connection dropped mid-session (network blip, app backgrounded, desktop asleep) and the terminal/composer attempted to write. Without failWhenDisconnected these would hang on waitForConnected (up to options.timeoutMs or the GIVE_UP_AFTER_ATTEMPTS cap) and then replay — for interactive input that's worse than failing fast.","solutions":["If the call is idempotent/non-interactive, remove failWhenDisconnected to let it wait for reconnect.","If interactive, surface 'Reconnecting…' to the user and queue the input locally for re-send once onStateChange reports 'connected'.","Check client.getState() before calling — if not 'connected', decide wait-vs-fail explicitly rather than relying on the option.","Subscribe to client.onStateChange to gate interactive sends on the connected state."],"exampleFix":"// before — interactive send fails fast on disconnect\nawait client.sendRequest('terminal.write', bytes, { failWhenDisconnected: true })\n\n// after — gate on connection state, queue while disconnected\nif (client.getState() !== 'connected') {\n  pendingWrites.push(bytes) // replayed on 'connected' state change\n  return\n}\nawait client.sendRequest('terminal.write', bytes)","handlingStrategy":"validation","validationCode":"// Before sendRequest with failWhenDisconnected, check state\nfunction canSendNow(client: RpcClient): boolean {\n  return client.getState() === 'connected'\n}\n// usage:\nif (options.failWhenDisconnected && !canSendNow(client)) {\n  // queue or surface 'reconnecting' instead of letting sendRequest throw\n  pendingWrites.push({ method, params })\n  return\n}","typeGuard":"function isNotConnectedError(error: unknown): boolean {\n  return error instanceof Error && /^Not connected: /.test(error.message)\n}","tryCatchPattern":"try {\n  await client.sendRequest(method, params, { failWhenDisconnected: true })\n} catch (error) {\n  if (isNotConnectedError(error)) {\n    pendingWrites.push({ method, params })\n    client.onStateChange(function listener(state) {\n      if (state === 'connected') {\n        client.onStateChange.removeListener?.(listener)\n        void flushPending()\n      }\n    })\n    return\n  }\n  throw error\n}","preventionTips":["Subscribe to client.onStateChange and gate interactive sends on state === 'connected'.","Only set failWhenDisconnected for calls whose semantics break under delayed replay (terminal writes, composer sends).","Queue interactive input locally while disconnected and replay on reconnect."],"tags":["rpc","network","connection-state","terminal","fail-fast"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}