Private
Public Access
0
0

refactor(ai_client): apply Re-Raise Pattern 1 to 4 RETHROW sites (Phase 12)

Per styleguide §7.6 Pattern 1: 'catch + convert + raise as different type'
requires 'raise X from e' to preserve the original exception in the
traceback.

Sites updated:

Site 1 (L277 _load_credentials):
  except FileNotFoundError as e:
      raise FileNotFoundError(f'...') from e

Sites 2+3 (L878+L879 _default_send, nested in run_with_tool_loop):
  if not res.ok:
      raise res.errors[0].original from None
      raise RuntimeError(...) from None
  The exceptions come from a Result, not a local except; 'from None'
  suppresses the implicit context.

Site 5 (L2061 _send inside _send_gemini_cli):
  raise cast(Exception, send_result.errors[0].original) from None

Site 6 (L2742 _dashscope_call):
  raise classify_dashscope_error(_dashscope_exception_from_response(resp)) from None

KNOWN LIMITATION: the audit script does not have a heuristic for
'raise X from e' / 'from None' (Pattern 1). The sites remain
INTERNAL_RETHROW in the audit. INTERNAL_RETHROW is 'suspicious but
not violation' (strict mode accepts). Adding a heuristic requires
Tier 1 approval per the conventions.

Audit: ai_client RETHROW 6 -> 5 (site 4 migrated separately; these
4 sites stay as INTERNAL_RETHROW by audit classification but follow
Pattern 1 by styleguide).
This commit is contained in:
2026-06-20 15:48:00 -04:00
parent d209c78b1c
commit 37ece145fa
2 changed files with 91 additions and 10 deletions
+64
View File
@@ -0,0 +1,64 @@
"""Phase 12 sites 1, 2+3, 5, 6: Pattern 1 (catch + raise from X) fixes.
Site 1 (_load_credentials):
except FileNotFoundError:
raise FileNotFoundError(f"...")
Missing `from e`; per styleguide Pattern 1 requires `raise X from e`.
Sites 2+3 (_default_send):
if not res.ok:
if res.errors and res.errors[0].original:
raise res.errors[0].original # site 2
raise RuntimeError(res.errors[0].message ...) # site 3
Missing `from None`; exception comes from a Result, not a local except.
Site 5 (_send inside _send_gemini_cli):
if not send_result.ok:
raise cast(Exception, send_result.errors[0].original)
Missing `from None`.
Site 6 (_dashscope_call):
if getattr(resp, "status_code", 200) != 200:
raise classify_dashscope_error(...)
Missing `from None`.
"""
import sys
sys.path.insert(0, ".")
def test_phase12_site1_load_credentials_has_from_e():
import inspect
import src.ai_client
src_text = inspect.getsource(src.ai_client._load_credentials)
assert "raise FileNotFoundError" in src_text
# Per Pattern 1: catch + convert + raise must use 'from e'
assert "from e" in src_text, \
"_load_credentials raise must use 'from e' (Pattern 1)"
def test_phase12_sites23_default_send_has_from_none():
import inspect
import src.ai_client
# _default_send is a nested function inside run_with_tool_loop; get source from the parent
src_text = inspect.getsource(src.ai_client.run_with_tool_loop)
# The nested _default_send must have 'from None' on its raises
assert "raise res.errors[0].original from None" in src_text, \
"_default_send original-exception raise must use 'from None'"
assert 'raise RuntimeError(res.errors[0].message if res.errors else "Unknown OpenAI error") from None' in src_text, \
"_default_send RuntimeError raise must use 'from None'"
def test_phase12_site5_send_cli_has_from_none():
import inspect
import src.ai_client
src_text = inspect.getsource(src.ai_client._send_gemini_cli)
assert "from None" in src_text, \
"_send_gemini_cli inner _send raise must use 'from None'"
def test_phase12_site6_dashscope_call_has_from_none():
import inspect
import src.ai_client
src_text = inspect.getsource(src.ai_client._dashscope_call)
assert "from None" in src_text, \
"_dashscope_call raise must use 'from None'"