different-ai/openwork · error

OpenWork host connection is not ready.

Error message

OpenWork host connection is not ready.

What it means

connectRealtime in VoicePanel depends on props.client, the OpenWork host connection object used to mint voice realtime sessions. If it is null/undefined the panel cannot create a realtime session, so it throws before doing any network work.

Source

Thrown at apps/app/src/react-app/domains/session/voice/voice-panel.tsx:483

        voiceRealtime.pendingResponse = false;
        requestRealtimeResponse(channel, false);
      } else {
        setRuntimeStatus(voiceRealtime.micMuted ? "muted" : "listening");
      }
      return;
    }
    if (type === "error") {
      voiceRealtime.responseInProgress = false;
      const error = readRecord(event, "error");
      const message = typeof error.message === "string" ? error.message : "Realtime returned an error.";
      addEntry("system", message, { error: true });
      setRuntimeStatus("error", message);
    }
  }, [addEntry, props.sessionId, requestRealtimeResponse, setRuntimeStatus]);

  const connectRealtime = useCallback(async (audioInput = true) => {
    const client = props.client;
    if (!client) throw new Error("OpenWork host connection is not ready.");
    if (audioInput && !navigator.mediaDevices?.getUserMedia) throw new Error("Microphone capture is unavailable in this runtime.");

    disconnectRealtime(true);
    setRuntimeStatus("connecting", "Minting Realtime session...");
    const sessionContext = await loadVoiceSessionContext(props.opencodeBaseUrl, props.openworkToken, props.sessionId);
    const realtimeSession = await client.createVoiceRealtimeSession({ sessionContext });

    const peer = new RTCPeerConnection();
    voiceRealtime.peer = peer;
    if (audioInput) {
      setRuntimeStatus("connecting", "Requesting microphone...");
      const macPermissionGranted = await requestMacMicrophoneAccess();
      if (!macPermissionGranted) throw new Error("macOS denied microphone access. Enable OpenWork in System Settings > Privacy & Security > Microphone, then restart OpenWork.");
      const stream = await navigator.mediaDevices.getUserMedia({
        audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true },
      });
      voiceRealtime.stream = stream;
      setMicDiagnostics(stream);

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Wait for the host client to be ready (disable the voice button until props.client is set)
  2. Reconnect the app to the OpenWork host / restart the desktop app if the connection dropped
  3. Guard the call: if (!client) show a 'connecting…' or retry state instead of throwing raw
  4. Check host process health/logs if client is persistently null

Example fix

// before
const client = props.client;
if (!client) throw new Error("OpenWork host connection is not ready.");
// after
const client = props.client;
if (!client) {
  setRuntimeStatus("error", "Host connection not ready — retrying...");
  return;
}
Defensive patterns

Strategy: type-guard

Validate before calling

if (!props.client) {
  setRuntimeStatus("idle", "Host connection not ready.");
  return;
}

Type guard

const hasHostClient = (c: typeof props.client): c is NonNullable<typeof props.client> => c != null;

Try / catch

try {
  await connectRealtime();
} catch (e) {
  if (e instanceof Error && e.message === "OpenWork host connection is not ready.") {
    setRuntimeStatus("error", "Reconnect to the OpenWork host and try again.");
  } else throw e;
}

Prevention

When it happens

Trigger: Calling connectRealtime() when props.client is null — typically because the voice panel rendered before the host client finished connecting, or the host (desktop/opencode backend) connection dropped.

Common situations: User clicks the voice connect button while the app is still establishing its host connection; host process crashed or restarted; panel mounted in a state where client was never injected (e.g. headless/web runtime without the bridge).

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/f901d7813903e8a1. Report an issue: GitHub.