Skip to content

Troubleshooting

The HMAC handshake failed. Causes:

  • Secret mismatch - WORKER_SHARED_SECRET does not equal the value StandIn holds from pairing. They must match exactly.
  • Clock skew - the timestamp is outside the freshness window (HMAC_FRESHNESS_MS, default 60 s). Sync the clocks (NTP).
  • Replayed handshake - the same (callId, ts, sig) tuple was already used. This is the single-use guard doing its job; a genuine retry uses a fresh timestamp.
  • Secret unset - the bridge fails closed if WORKER_SHARED_SECRET is empty; every upgrade is rejected.

A live session already owns that call id (a retry or rollout reconnect). The bridge rejects the duplicate so it does not open a second billed Realtime session for one call. It clears when the first session tears down (a silent dead peer clears after the 90 s idle window).

A connection cap was hit: MAX_CONNECTIONS (default 64) or MAX_CONNECTIONS_PER_IP (default = the total cap). Raise them for a busier deployment, or check for a client that is not closing sockets.

The bridge could not open the OpenAI Realtime session (it retries once). Check that OPENAI_API_KEY is valid and has Realtime access, and that OPENAI_REALTIME_MODEL names a model your account can use. The log line carries the underlying error (401, 404 model not found, connect timeout).

Call ends at ~60 minutes with agent-disconnected

Section titled “Call ends at ~60 minutes with agent-disconnected”

The OpenAI Realtime API caps a session at 60 minutes server-side; when it closes the socket, the bridge can only end the Teams call. There is no session migration. Set MAX_CALL_MINUTES below 60 so the bridge governor speaks a goodbye first - the bridge warns about this at startup when the governor would not fire in time.

A goodbye was spoken (StandIn-side assistant.say) but nobody tore the call down within the grace window plus the hard cap. This is the bridge’s backstop doing its job; check the StandIn connection if it recurs.

MAX_CALL_MINUTES must be a number. A non-numeric or negative value stops startup with a clear error (numeric env vars fail loud), so if the process started, the value parsed. Confirm it is greater than 0 (0 disables the governor).

OPENAI_HOST is restricted to *.openai.com so the API key can only be sent to OpenAI. Set OPENAI_HOST_ALLOW_ANY=true only for a proxy you control.

Startup error about OPENAI_VAD, OPENAI_MCP_SERVERS, or VISION_API_URL

Section titled “Startup error about OPENAI_VAD, OPENAI_MCP_SERVERS, or VISION_API_URL”

All three are validated at startup and fail loud: OPENAI_VAD must be server_vad or semantic_vad; OPENAI_MCP_SERVERS must be a JSON array of {server_label, server_url} entries with https URLs; VISION_API_URL must be a well-formed http(s) URL without embedded credentials (a private-IP host is allowed for local endpoints, with a warning).

The URL is SSRF-guarded: public hosts only, at most one redirect hop (re-validated), 5 MB and 10 s bounds, jpeg/png only. A private/metadata address, a second redirect, or an oversized body is refused by design - the agent receives the failure as its tool result and can tell the caller.

The CLI prints a friendly hint on EADDRINUSE. Set PORT to a free port.

The bridge logs one line per event to stdout/stderr, scoped by call id. Set LOG_LEVEL=debug for the verbose relay detail (an invalid value falls back to info). Transcript logging additionally requires LOG_TRANSCRIPTS=true, Teams recording to be active, and (for user turns) OPENAI_TRANSCRIPTION_MODEL.