fix(chat): guard final_response so empty runs fail cleanly, not KeyError - #251
Merged
Merged
Conversation
AIAgent.chat() did `return result["final_response"]`. run_conversation has
several early-return / error paths (interrupted, retries exhausted, policy or
billing bail) that omit "final_response", so when a local model never commits
a final turn, chat() crashed with `KeyError: 'final_response'`.
In the MeshBoard dispatch path (`hermes -z "/goal ..."` goal loop), that
traceback surfaced as harness/hermes_exited_with_stderr and was counted as a
0%-success harness fault — when the real condition is just "no final response
produced", which the codebase already treats as a clean failed run elsewhere.
Honor the documented `-> str` contract: `return result.get("final_response")
or ""`. Also guard the __main__ summary print. Empty string flows upstream as
a normal failed run instead of a Python crash, so the autonomy metric classes
it correctly (worker produced nothing) rather than as a launcher/harness bug.
Tests: tests/test_chat_final_response_guard.py — chat() returns "" when the
key is missing or None, and the value when present.
Co-Authored-By: Claude Code <noreply@anthropic.com>
🔎 Lint report:
|
| Rule | Count |
|---|---|
unresolved-attribute |
2 |
First entries
run_agent.py:3004: [unresolved-attribute] unresolved-attribute: Object of type `Self@get_credits_spent_micros` has no attribute `_credits_session_start_micros`
tests/run_agent/test_credits_notices_toggle.py:76: [unresolved-attribute] unresolved-attribute: Unresolved attribute `_credits_session_start_micros` on type `AIAgent`
✅ Fixed issues (1):
| Rule | Count |
|---|---|
invalid-assignment |
1 |
First entries
tests/run_agent/test_credits_notices_toggle.py:76: [invalid-assignment] invalid-assignment: Object of type `None` is not assignable to attribute `_credits_session_start_micros` of type `int`
Unchanged: 5708 pre-existing issues carried over.
Diagnostics are surfaced as warnings — this check never fails the build.
The --environment jsdom flag in the npm script was not being honored by vitest v4. The window and requestAnimationFrame globals were not being defined, causing unhandled ReferenceError exceptions in tests that import DOM-dependent libraries like react-dom and use-stick-to-bottom. Add a test.environment: jsdom block to vite.config.ts so vitest picks up the jsdom environment correctly for all tests in apps/desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
AIAgent.chat()(run_agent.py) didreturn result["final_response"].run_conversation(forwarded toagent/conversation_loop.py) has several early-return / error paths — interrupted, retries exhausted, policy/billing bail — that return a dict withoutfinal_response. When a local model never commits a final turn,chat()crashed withKeyError: 'final_response'.In MeshBoard's dispatch path the worker runs
hermes -z "/goal ...", whose goal loop callschat(). The KeyError traceback surfaced asharness/hermes_exited_with_stderrand was counted as a 0%-success harness fault in the autonomy metric — but the real condition is simply "no final response produced," which the codebase already treats as a clean failed run elsewhere (hermes -z: no final response was produced; treating the run as failed.).What changed
run_agent.pychat():return result.get("final_response") or ""— honors the documented-> strcontract; a run that produced no final turn returns""instead of raising.run_agent.py__main__summary: guard the sameresult['final_response']read.tests/test_chat_final_response_guard.py: new.How to test
Evidence
The three cases:
final_responsekey missing →""; key present butNone→""; key present with text → that text.Risks / gaps
final_responsenow returns""rather than raising. Callers already treat a falsy final response as "no response" (the__main__summary only prints when truthy), so this aligns with existing handling. Accepted.hermes -zdispatch was not re-run live (requires a local model lane); the unit test exercises the exactchat()contract that crashed. Reasonable given the change is a one-line defensive guard.