From: Simon Glass <sjg@chromium.org>
The Claude Agent SDK raises a bare Exception on API errors (e.g. 500
Internal Server Error), but run_agent_collect() only catches
RuntimeError, ValueError and OSError. This causes the entire review to
crash when a single agent call fails, even though the review loop
already handles the failure gracefully with a placeholder message.
Catch bare Exceptions whose message indicates an API or process error
(containing 'API Error' or 'exit code') while re-raising unexpected
exceptions that indicate real bugs.
Signed-off-by: Simon Glass <sjg@chromium.org>
---
tools/u_boot_pylib/claude.py | 5 +++++
tools/u_boot_pylib/test_claude.py | 31 +++++++++++++++++++++++++++++++
2 files changed, 36 insertions(+)
@@ -62,3 +62,8 @@ async def run_agent_collect(prompt, options):
except (RuntimeError, ValueError, OSError) as exc:
tout.error(f'Agent failed: {exc}')
return False, '\n\n'.join(conversation_log)
+ except Exception as exc:
+ if 'API Error' in str(exc) or 'exit code' in str(exc):
+ tout.error(f'Agent failed: {exc}')
+ return False, '\n\n'.join(conversation_log)
+ raise
@@ -83,6 +83,37 @@ class TestClaude(unittest.TestCase):
self.assertFalse(success)
+ def test_run_agent_collect_handles_api_error(self):
+ """run_agent_collect() catches SDK API errors"""
+ # pylint: disable=W0613,W0719
+ async def mock_query(**kwargs):
+ raise Exception(
+ 'Command failed with exit code 1 (exit code: 1)')
+ yield # pylint: disable=W0101
+
+ self._setup_claude_with_mock_query(mock_query)
+ loop = asyncio.new_event_loop()
+ with terminal.capture():
+ success, _ = loop.run_until_complete(
+ claude.run_agent_collect('test prompt', MagicMock()))
+ loop.close()
+
+ self.assertFalse(success)
+
+ def test_run_agent_collect_reraises_unknown(self):
+ """run_agent_collect() re-raises unexpected exceptions"""
+ # pylint: disable=W0613
+ async def mock_query(**kwargs):
+ raise TypeError('unexpected bug')
+ yield # pylint: disable=W0101
+
+ self._setup_claude_with_mock_query(mock_query)
+ loop = asyncio.new_event_loop()
+ with self.assertRaises(TypeError):
+ loop.run_until_complete(
+ claude.run_agent_collect('test prompt', MagicMock()))
+ loop.close()
+
def test_run_agent_collect_skips_non_text_blocks(self):
"""run_agent_collect() ignores blocks without text attribute"""
text_block = MagicMock()