Summary
inkbox-claude bootstrap without --voice-ai produces a gateway that crashes on startup on any machine that has OPENAI_API_KEY exported in the environment (very common — LiteLLM proxies, other AI tooling, shell profiles). The daemon exits and bootstrap/start report Claude Code gateway did not become ready.
Environment
inkbox-claude installed via the curl | bash installer, app at ~/.inkbox-claude/app (venv-isolated), Python 3.13, macOS.
OPENAI_API_KEY present in the environment the detached daemon inherits (in our case exported by an unrelated LiteLLM rotation proxy in a shell profile). No voice/realtime config in ~/.inkbox-claude/.env.
Steps to reproduce
- Have
OPENAI_API_KEY exported in the environment.
inkbox-claude bootstrap --identity <handle> --start-gateway without --voice-ai.
- Gateway fails to become ready; check
~/.inkbox-claude/gateway.log.
Expected
With no voice flags and no realtime config, the gateway should start with a non-voice/inkbox_tts_stt stack and ignore an unrelated ambient OPENAI_API_KEY.
Actual
bootstrap returns {"status":"error","error":"Claude Code gateway did not become ready"}.
gateway.log shows: RuntimeError: INKBOX_VOICE_STACK=openai_realtime requires INKBOX_REALTIME_API_KEY raised in gateway.py run().
inkbox-claude doctor confusingly reports phone voice stack openai_realtime and OpenAI Realtime key set even though nothing voice-related is configured in .env.
Root cause (from reading the app source)
bootstrap only writes INKBOX_REALTIME_ENABLED=false inside _configure_voice(), which is gated behind --voice-ai (bootstrap.py, ~L137 / ~L211). Without --voice-ai, that line is never written.
- With the flag absent,
resolve_voice_stack() in config.py falls through to a heuristic: if any OPENAI_API_KEY is in the environment, assume voice_stack=openai_realtime.
_read_realtime_config() then reads INKBOX_REALTIME_API_KEY or OPENAI_API_KEY, so the ambient key satisfies the key check — but realtime.enabled is still False (needs INKBOX_REALTIME_ENABLED=true), so the gateway's startup invariant raises and the daemon exits. doctor sees the same ambient key, hence its misleading output.
Workaround (config only, survives plugin updates)
printf '%s\n' 'INKBOX_REALTIME_ENABLED=false' >> ~/.inkbox-claude/.env
inkbox-claude restart
resolve_voice_stack then returns inkbox_tts_stt before it ever consults OPENAI_API_KEY.
Suggested fix
Don't infer openai_realtime from a bare ambient OPENAI_API_KEY. Gate the realtime stack on an explicit opt-in (INKBOX_REALTIME_ENABLED=true or an Inkbox-specific INKBOX_REALTIME_API_KEY), and/or write INKBOX_REALTIME_ENABLED=false during a non-voice bootstrap so the default install is deterministic regardless of unrelated env vars.
Summary
inkbox-claude bootstrapwithout--voice-aiproduces a gateway that crashes on startup on any machine that hasOPENAI_API_KEYexported in the environment (very common — LiteLLM proxies, other AI tooling, shell profiles). The daemon exits andbootstrap/startreportClaude Code gateway did not become ready.Environment
inkbox-claudeinstalled via thecurl | bashinstaller, app at~/.inkbox-claude/app(venv-isolated), Python 3.13, macOS.OPENAI_API_KEYpresent in the environment the detached daemon inherits (in our case exported by an unrelated LiteLLM rotation proxy in a shell profile). No voice/realtime config in~/.inkbox-claude/.env.Steps to reproduce
OPENAI_API_KEYexported in the environment.inkbox-claude bootstrap --identity <handle> --start-gatewaywithout--voice-ai.~/.inkbox-claude/gateway.log.Expected
With no voice flags and no realtime config, the gateway should start with a non-voice/
inkbox_tts_sttstack and ignore an unrelated ambientOPENAI_API_KEY.Actual
bootstrapreturns{"status":"error","error":"Claude Code gateway did not become ready"}.gateway.logshows:RuntimeError: INKBOX_VOICE_STACK=openai_realtime requires INKBOX_REALTIME_API_KEYraised ingateway.pyrun().inkbox-claude doctorconfusingly reportsphone voice stack openai_realtimeandOpenAI Realtime key seteven though nothing voice-related is configured in.env.Root cause (from reading the app source)
bootstraponly writesINKBOX_REALTIME_ENABLED=falseinside_configure_voice(), which is gated behind--voice-ai(bootstrap.py, ~L137 / ~L211). Without--voice-ai, that line is never written.resolve_voice_stack()inconfig.pyfalls through to a heuristic: if anyOPENAI_API_KEYis in the environment, assumevoice_stack=openai_realtime._read_realtime_config()then readsINKBOX_REALTIME_API_KEY or OPENAI_API_KEY, so the ambient key satisfies the key check — butrealtime.enabledis stillFalse(needsINKBOX_REALTIME_ENABLED=true), so the gateway's startup invariant raises and the daemon exits.doctorsees the same ambient key, hence its misleading output.Workaround (config only, survives plugin updates)
resolve_voice_stackthen returnsinkbox_tts_sttbefore it ever consultsOPENAI_API_KEY.Suggested fix
Don't infer
openai_realtimefrom a bare ambientOPENAI_API_KEY. Gate the realtime stack on an explicit opt-in (INKBOX_REALTIME_ENABLED=trueor an Inkbox-specificINKBOX_REALTIME_API_KEY), and/or writeINKBOX_REALTIME_ENABLED=falseduring a non-voice bootstrap so the default install is deterministic regardless of unrelated env vars.