Repository navigation
fix(agent-api): guard voice_desired_state toggle with a Lock (closes #1922) - #1975
Closed
john-the-dev wants to merge 1 commit into
Closed
john-the-dev wants to merge 1 commit into
john-the-dev wants to merge 1 commit into
Conversation
…onichi#1922) /voice/toggle has a read-modify-write on voice_desired_state. With ThreadingHTTPServer (PR sonichi#1921), two simultaneous POSTs can interleave and double-toggle back to the original state. Add _voice_state_lock (threading.Lock) and acquire it around the toggle's read-then-assign. Harmless with the current single-threaded HTTPServer; necessary when sonichi#1921 lands. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QB3qZQrwponfum2JFNvHeh
Collaborator
Author
Contributor
Coverage Gate❌ Diff coverage BELOW the 95% bar — uncovered changed lines listed below. Whole-tree (informational): 33%. Diff CoverageDiff: origin/main...HEAD, staged and unstaged changes
Summary
src/agent-api.pyLines 764-773 764
765 if path == "/voice/toggle":
766 if not self.check_auth():
767 return
! 768 with _voice_state_lock:
! 769 voice_desired_state = "connected" if voice_desired_state == "disconnected" else "disconnected"
770 self.send_json(200, {"state": voice_desired_state})
771 return
772
773 if path == "/voice/set": |
john-the-dev
added a commit
to john-the-dev/sutando
that referenced
this pull request
Jul 7, 2026
…ge gate Adds tests/agent-api-voice-toggle.test.py to exercise the `with _voice_state_lock:` block added in sonichi#1975. Follows the main-thread-server pattern from agent-api-delegation.test.py so the coverage tracer records the handler dispatch on the main (traced) thread. Checks: initial state is "disconnected", first toggle → "connected", second toggle → "disconnected", lock object is a real threading.Lock. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QB3qZQrwponfum2JFNvHeh
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.
Summary
/voice/togglehad a read-modify-write onvoice_desired_state: read current value, compute toggle, assign back — all unprotected.ThreadingHTTPServer(PR fix(agent-api): ThreadingHTTPServer + handler timeout so one stalled client can't wedge the API #1921), two simultaneous POSTs can interleave and double-toggle, landing the UI on the wrong state._voice_state_lock = threading.Lock()(module-level, near thevoice_desired_statedeclaration) and acquire it around the toggle's read-then-assign.HTTPServer; necessary when fix(agent-api): ThreadingHTTPServer + handler timeout so one stalled client can't wedge the API #1921 lands./voice/setplain assignment stays unlocked — single assignment is atomic under the GIL and has no read-modify cycle.Test plan
python3 tests/agent-api-delegation.test.py— PASS (no regression in the endpoint test suite)python3 tests/agent-api-task-field-injection.test.py— PASS🤖 Generated with Claude Code
https://claude.ai/code/session_01QB3qZQrwponfum2JFNvHeh