Skip to content

Live: a tool that never answers no longer mutes the model for the rest of the night - #36

Merged
jayzuccarelli merged 1 commit into
mainfrom
live-stuck-tool-calls
Sep 22, 2026
Merged

jayzuccarelli merged 1 commit into
mainfrom
live-stuck-tool-calls

Conversation

@jayzuccarelli

@jayzuccarelli jayzuccarelli commented Sep 22, 2026 •

Copy link
Copy Markdown
Owner

One wake launched an app on the TV and then said nothing. So did the next one, and the one after that.

Cause. The MCP call is awaited with no timeout, so a Home Assistant tool that takes a request and never returns leaves its function call open. The Live API refuses every response.create while one is unanswered (function_call_outputs_required), and that refusal is fatal to the service. Open calls are cleared only when the socket drops, and the socket is kept warm between wakes on purpose, so one tool that did not come back mutes every wake after it.

Fix. Exactly one output per call is the API's invariant; the broker now keeps it whatever the tool does. A call that has not come back in six seconds is answered here with an error. A call still open when a new wake begins is dropped. The backstop waits for open calls before asking for a reply.

Evidence. From the output tapes of the evening this came from: of three wakes, two played nothing at all to the device. After the change, two three-turn conversations delivered every answer, with only filler dropped. 21 hygiene tests green.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HMrdB2Qn92kNxGz9Ps2iXj

Summary by CodeRabbit

  • Bug Fixes
    • Prevented voice sessions from going silent indefinitely when delegated tools fail to respond.
    • Added automatic fallback responses for tool calls that remain open too long.
    • Improved handling when users speak while a tool call is still pending, avoiding rejected responses and stalled interactions.
    • Ensured pending tool calls are cleared when a live session resumes.

…t of the night

One wake launched an app on the TV and then said nothing. The next wake
said nothing either, and the one after that. The device did what it was
asked and the room heard silence.

The MCP call is awaited with no timeout, so a Home Assistant tool that
takes a request and never returns leaves its function call open. The Live
API refuses every `response.create` while one is unanswered
("function_call_outputs_required"), and the refusal is fatal to the
service: the pipeline is torn down and rebuilt. Worse, the open calls are
cleared only when the socket drops, and the socket is deliberately kept
warm between wakes, so one tool that did not come back is enough to mute
every wake after it.

Exactly one output per call is the API's invariant, so the broker keeps
it whatever the tool does:

- a call that has not come back within six seconds is answered here, with
  an error, so the model can speak; Home Assistant answers in
  milliseconds, and six seconds still leaves room to reply inside the
  hygiene watcher's twelve;
- a call still open when a new wake begins belonged to a wake that is
  over, and is dropped rather than carried forward;
- and the backstop waits for open calls before asking for a reply, so a
  correction is never sent over the top of a tool call.

Read off the output tapes of the evening this came from: of three wakes,
two played nothing at all to the device, and the third answered only
after being sworn at. After the change, two three-turn conversations
delivered every answer, with only filler dropped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HMrdB2Qn92kNxGz9Ps2iXj
@jayzuccarelli

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 9625c9be-9876-4606-8ea8-656b7a063e2b

📥 Commits

Reviewing files that changed from the base of the PR and between 34f9a72 and 8034d3a.

📒 Files selected for processing (2)
  • broker/realtime_broker/live_agent.py
  • broker/tools/test_live_hygiene.py

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The Live service now times out unanswered tool calls, waits for open calls before creating responses, clears pending calls at session start, and adds regression coverage for these behaviors.

Changes

Live tool-call watchdog

Layer / File(s) Summary
Stalled tool-call answering
broker/realtime_broker/live_agent.py
The service schedules _answer_if_stuck for each tool call. After six seconds, it sends an error output or removes the call when no device is present.
Response and session synchronization
broker/realtime_broker/live_agent.py
_verify_utterance and _unreadable wait for open calls before creating responses. begin_live_session clears pending calls.
Watchdog regression coverage
broker/tools/test_live_hygiene.py
The test verifies single answers, cleanup without a device, and bounded waiting for open calls.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Medium

Sequence Diagram(s)

sequenceDiagram
  participant LiveAPI
  participant VoicePELiveService
  participant ToolWatchdog
  LiveAPI->>VoicePELiveService: delegated tool call
  VoicePELiveService->>ToolWatchdog: schedule _answer_if_stuck
  ToolWatchdog->>LiveAPI: send error function-call output
  VoicePELiveService->>LiveAPI: create response after open calls close
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 53.85% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 13 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: preventing unanswered Live tool calls from muting the model. It is specific, concise, and directly related to the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai

coderabbitai Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@jayzuccarelli
jayzuccarelli merged commit d28e8d8 into main Sep 22, 2026
3 checks passed
@jayzuccarelli
jayzuccarelli deleted the live-stuck-tool-calls branch September 22, 2026 00:19
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.

1 participant