fix: clear the 10 open SonarCloud issues, incl. a quadratic regex on the TTS path (0.54.3)#157
Conversation
All ten were pre-existing; the quality gate was already PASSED, so this is
debt paydown rather than a gate fix. Clearing them now avoids the per-PR
Sonar round-trip on every future PR that touches these files.
The S8786 finding turned out to be more than a style nit. `trailing_pause_ms`
matched `!{3,}$` against sentence text, which backtracks per start position
when a long "!" run is NOT at the end. Measured worst case on 40k characters:
regex 2649 ms vs rstrip 0.0055 ms (~480,000x)
That text is model output or caller-supplied `/v1/audio/speech` input, so the
pathological case is reachable. Replaced with a linear rstrip count, and
proved the rewrite behaviour-identical by differential testing both
implementations over 209,331 strings (exhaustive to length 5 over the
punctuation alphabet, plus 200k random) — zero mismatches.
S3776: _synthesize_single carried cognitive complexity 30 against a limit of
15. Extracted _handle_tts_response (the response-validation branch tree) plus
the pure _is_truncated / _min_plausible_duration helpers. A _RETRY sentinel
distinguishes "retry within the same semaphore hold" from b"" ("give up") —
an empty-bytes retry would read as "no audio" to the caller.
Remaining eight are mechanical: chained endswith -> tuple form (S8513 x2),
log.exception in the catch-all arm (S8572), FastAPI Annotated hints with
`model` correctly typed `str | None` (S8410 x3), and hoisting non-asserting
setup out of pytest.raises so the tests assert what they claim (S5778 x2).
tts_client.py imports httpx at module top, so the offline suite cannot import
it and these helpers cannot be measured there. The new test file uses the
repo's existing importorskip pattern: it skips offline and passes (21 tests)
wherever the [realtime] extra is installed. Extracting the pure logic into a
stdlib module so the offline suite covers it is a worthwhile follow-up.
Verified: 2582 passed / 15 skipped, black + isort + flake8 clean, bandit 0
issues, afi cli doctor --strict PASS.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JivjPuXSzBAwjDcEyURyWM
|
/agentic_review |
PR Summary by QodoClear SonarCloud findings; harden TTS pause detection and refactor synth response flow
AI Description
Diagram
High-Level Assessment
Files changed (8)
|
Bare _handle_tts_response / _is_truncated / _min_plausible_duration were parsed as emphasis markers with spaces. Backticks are correct anyway — they are code identifiers. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JivjPuXSzBAwjDcEyURyWM
Code Review by Qodo
1.
|
Qodo review comment 3647941477 on PR #157. The S3776 complexity fix in 0.54.3 split _handle_tts_response() out of _synthesize_single(), but typed it as returning `object` and suppressed the resulting mismatch at the call site with `# type: ignore[return-value]`. That left "callers only ever see bytes" as an assertion rather than a checked invariant, so a future edit could leak a non-bytes sentinel past the retry check without a static error. Replace the bare `_RETRY = object()` with a dedicated `_Retry` sentinel type, annotate the helper `bytes | _Retry`, and narrow at the call site with `isinstance` — identity against a module global does not narrow a union for a type checker, so `is _RETRY` would still have needed the suppression. Runtime behaviour is unchanged: the same singleton is returned and compared, and both retry paths still continue the loop. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KK2FrZ6N7q2kdNTcQsqmzb
# Conflicts: # CHANGELOG.md # pyproject.toml
The conflict resolution joined the 0.54.3 list directly onto main's 0.54.2 heading (MD032/MD022). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F7m3KqJ5MyxnxUuqemYLTB
|



Clears all 10 open SonarCloud issues. The quality gate was already
PASSED— every one of these is pre-existing debt in older code, so this is paydown, not a gate fix. Clearing them now removes the per-PR Sonar round-trip on any future PR that touchestts_client.pyorapp.py.One of them was not a style nit
trailing_pause_msmatched!{3,}$against sentence text. That pattern backtracks per start position when a long!run is not at the end. Measured worst case:The text reaching this function is model output or caller-supplied
/v1/audio/speechinput, so the pathological case is reachable — this is a ReDoS-shaped stall on the TTS path, not a lint preference. Sonar rated it MAJOR; in context it is the most consequential item here.Replaced with a linear
rstrip("!")count. Because the function has no existing test (see below), I proved the rewrite behaviour-identical by differential-testing both implementations:!?.…a<space>209,331 strings, zero mismatches.
Cognitive complexity (S3776)
_synthesize_singlecarried complexity 30 against a limit of 15. Extracted:_handle_tts_response(...)— the response-validation branch tree (status → empty body → truncation)_is_truncated(clean, duration)and_min_plausible_duration(clean)— pure, and now directly testableA
_RETRYsentinel distinguishes "this attempt failed, retry within the same semaphore hold" fromb""("give up"). Using empty bytes for both would have made a retry indistinguishable from "no audio" to the caller. Retry/reset semantics and the semaphore-hold guarantee are unchanged.The mechanical eight
S8513×2endswith→ single tuple-argument callS8572log.exceptionso the traceback survivesS8410×3Annotatedhints on/v1/audio/transcriptions;modelalso corrected fromstr = Form(None)tostr | NoneS5778×2pytest.raisesso the tests assert what they claimThe
S5778pair is worth a word: intest_benchmark_all_lobes.pythe block contained two calls, so the test would have passed had_make_all_lobes_argsthrown — it now asserts only what it means to.Testing gap, stated plainly
tts_client.pyimportshttpxat module top (realtime-container only), so the offline suite cannot import it — which is exactly why the file is insonar.coverage.exclusionsand whytrailing_pause_mshad no test.The new
tests/test_tts_pause_and_truncation.pyuses the repo's existingimportorskippattern (same astest_chatterbox_pcm16.pydoes for numpy): skips offline, 21 tests pass wherever the[realtime]extra is installed. Verified both ways. It includes a regression test asserting the 40k-character input completes in under 100 ms.This is honest but not ideal — these helpers are pure and stdlib-only, and the httpx import is the only reason they can't be measured offline. Extracting them into a stdlib module so the offline suite covers them is a worthwhile follow-up, deliberately not done here to keep this PR to the Sonar findings.
Verification
Note on ordering
Versioned 0.54.3, sequenced deliberately behind #156 (which is open and takes 0.54.2). Both branch from
mainat 0.54.1, so if this merges first #156 will need a re-bump; merging #156 first means this needs no rebase.No behaviour change intended
Every fix is refactor-or-equivalent. The one place behaviour could have drifted — the regex rewrite — is the one covered by the 209,331-string differential proof above.