Skip to content

fix(startup): let a minted managed Gemini credential enable voice - #2287

Closed
jsun-m wants to merge 1 commit into
sonichi:mainfrom
jsun-m:fix/startup-managed-voice-gate
Closed

jsun-m wants to merge 1 commit into
sonichi:mainfrom
jsun-m:fix/startup-managed-voice-gate

Conversation

@jsun-m

@jsun-m jsun-m commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Problem

configure_startup_runtime (src/startup-runtime.sh) decides SKIP_VOICE from the BYO env keys alone (GEMINI_VOICE_API_KEY / GEMINI_API_KEY). On a managed-mode install there are no env keys — the desktop app mints <workspace>/state/auth/managed-credentials.json (G8 contract, src/credential-resolver.ts) and voice-key.ts resolves it at runtime. Result: startup sets SKIP_VOICE=1 and the voice-agent never launches, even though it would connect fine.

Observed live 2026-07-23 on a managed-only install: credential minted and valid, voice-agent down; launching dist/voice-agent.js manually connected to Gemini Live immediately ([VoiceSession] LLM transport connected).

Fix

If no env key is present, check the managed file for a gemini-voice (fallback gemini-text) capability with a non-empty key — the same presence semantics the resolver uses. Expiry/refresh remain the resolver's concern. When the managed tier is what enabled voice, print ✓ voice credential: managed (…) per G8's source-observability goal.

Compatibility: with no managed file the gate behaves byte-identically to today, so this is inert on plain/BYOK installs and on main until the G8 writer lands (#2131 / #2262 / #2197 — none of which touch this gate).

Tests

Extended tests/startup-voice-optional.test.sh: a minted managed file enables voice (SKIP_VOICE=0 + managed message); a keyless managed file does not (SKIP_VOICE=1). All pre-existing cases pass unchanged.

🤖 Generated with Claude Code

configure_startup_runtime gated SKIP_VOICE on the BYO env keys alone, so a
managed-only install (G8: desktop mints <workspace>/state/auth/
managed-credentials.json, voice-key.ts resolves it at runtime) booted with
voice disabled even though the voice-agent connects fine when launched.
Observed live 2026-07-23: managed credential present, voice-agent down,
manual launch connected to Gemini Live immediately.

Teach the gate the managed tier as a second source, mirroring the
resolver's presence semantics (gemini-voice falls back to gemini-text,
non-empty key counts, expiry stays the resolver's concern). With no
managed file the behavior is byte-identical to the env-only chain.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Coverage Gate

✅ No Python changes — nothing to gate (bar: 95% on changed lines).

@sonichi

sonichi commented Jul 23, 2026

Copy link
Copy Markdown
Owner

The startup-gate logic is correct in isolation (fail-closed on malformed JSON / missing file, isinstance guard before .get("key"), env-key path preserved), and the sutando-config.sh workspace resolution matches the M0 contract. But there's a cross-PR merge-order hazard worth making explicit before this lands:

This gate depends on #2197 (feat/g8-reader-sweep), which is still open. On current main, src/voice-key.ts resolves env-only — GEMINI_VOICE_API_KEY → GEMINI_API_KEY → '' — and there is no src/credential-resolver.ts and no capabilities.* reader anywhere in src/. The runtime resolution this PR's body describes ("voice-key.ts resolves it at runtime") only becomes true after #2197, which rewrites voiceApiKey() to resolveCredential('gemini-voice').key with the managed(voice→text) → env chain.

Concretely, if #2287 merges before #2197, a managed-only install (minted managed-credentials.json, no env keys) would:

  1. startup: managed_gemini_credential_present() → true → unset SKIP_VOICE → voice-agent launches;
  2. runtime: voiceApiKey() still returns '' → voice-agent boots keyless and fails to connect.

That's a regression from today's clean SKIP_VOICE=1 (voice cleanly disabled). Recommend gating: land #2197 first, or merge the two together. A one-line "depends on #2197" in the body would make the ordering explicit for whoever merges.

Non-blocking (maintainability): the gate re-implements the G8 shape (capabilities.<slot>.key) as an inline python snippet rather than calling the resolver. Since #2197 owns that shape in credential-resolver.ts, this is a second copy in a fail-silent path — if the G8 shape ever changes, startup silently mis-detects presence (voice wrongly enabled/disabled) while the TS side stays correct. Worth having the resolver expose a presence/--check mode the shell can call, so there's a single source of truth. Fine to defer.

Not blocking on the code itself — flagging the dependency so it doesn't merge out of order.

@jsun-m jsun-m closed this Jul 24, 2026
@jsun-m
jsun-m deleted the fix/startup-managed-voice-gate branch July 24, 2026 00:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants