diff --git a/pydantic_ai_harness/experimental/overflow/_capability.py b/pydantic_ai_harness/experimental/overflow/_capability.py index 126ffee..10a2e3e 100644 --- a/pydantic_ai_harness/experimental/overflow/_capability.py +++ b/pydantic_ai_harness/experimental/overflow/_capability.py @@ -529,8 +529,17 @@ async def _read_slice( try: data = await store.read(handle) - except OSError as exc: - raise ModelRetry(f'No stored tool result for handle {handle!r}: {exc}.') from exc + except OSError: + # Return, not raise: a wrong handle (e.g. the model passing a tool-call id) or a + # result that is no longer stored must not consume a tool retry and escalate to a + # fatal `UnexpectedModelBehavior`. Guide the model to a valid handle instead. The + # exception is intentionally not echoed -- a store's error can carry the resolved + # filesystem path or other backend detail the model has no need for. + return ( + f'[No stored tool result for handle {handle!r}. Use the exact handle string from a ' + '"[Tool output too large ... stored to handle ...]" marker; if the result is no longer ' + 'available, re-run the original tool.]' + ) lines = data.decode('utf-8', errors='replace').splitlines() if pattern is not None: diff --git a/tests/experimental/overflow/test_overflow.py b/tests/experimental/overflow/test_overflow.py index 231c8e1..8d12e6b 100644 --- a/tests/experimental/overflow/test_overflow.py +++ b/tests/experimental/overflow/test_overflow.py @@ -697,9 +697,14 @@ class TestReadBack: assert len(out) < 60_000 async def test_read_slice_missing_handle(self, tmp_path: Path): + # A missing/wrong handle returns a guiding message (it does NOT raise): a bad + # handle must not consume a retry and escalate to a fatal UnexpectedModelBehavior. store = LocalFileStore(base_dir=tmp_path) - with pytest.raises(ModelRetry, match='No stored tool result'): - await _read_slice(store, 'missing/1.0', offset=0, limit=10, from_end=False, pattern=None) + out = await _read_slice(store, 'missing/1.0', offset=0, limit=10, from_end=False, pattern=None) + assert 'No stored tool result' in out + assert 're-run the original tool' in out + # The store's error (which can carry the resolved filesystem path) is not leaked. + assert str(tmp_path) not in out async def test_get_toolset_registers_read_tool(self, tmp_path: Path): store = LocalFileStore(base_dir=tmp_path)