retry makes too few attempts and hides the error

Done

/home/devdi/devswarm/evals/tasks/retry_attempts/repo · 2026-11-09 16:20:41 UTC · took 31.7s

  1. Plan (done)
  2. Code (done)
  3. Test (done)
  4. Review (done)
  5. Finish (done)
Cost
$0.0034
Tests
Passed
Iterations
1
Re-plans
0
LLM calls
11
Prompt read from cache
83%

Usage by agent

Cost per agent
planner: $0.0009 planner planner, Cost: $0.0009 $0.0009 coder: $0.0014 coder coder, Cost: $0.0014 $0.0014 tester: $0.0011 tester tester, Cost: $0.0011 $0.0011
Tokens per agent
  • Cache read
  • Cache write
  • Input
  • Output
planner: 7,859 planner planner, Cache read: 5,636 planner, Cache write: 709 planner, Output: 1,506 7,859 coder: 23.2K coder coder, Cache read: 18.4K coder, Cache write: 3,068 coder, Output: 1,745 23.2K tester: 16.3K tester tester, Cache read: 11.7K tester, Cache write: 3,525 tester, Output: 1,065 16.3K
View as table
AgentCostCache readCache writeInputOutput
planner$0.00095,63670981,506
coder$0.001418,3743,068121,745
tester$0.001111,6903,52581,065

Timeline

  1. planner iteration 1 Finished 3 calls · 7,859 tokens · $0.0009 · 11.3s

    Loading…

  2. coder iteration 1 Finished 5 calls · 23.2K tokens · $0.0014 · 11.4s

    Loading…

  3. tester iteration 1 Finished 3 calls · 16.3K tokens · $0.0011 · 7.9s

    Loading…

  4. verify iteration 1 Passed 0.5s
    .......                                                                  [100%]
    7 passed in 0.02s
    

Final diff

diff --git a/retrying.py b/retrying.py
index a49ffae..fbb3bcd 100644
--- a/retrying.py
+++ b/retrying.py
@@ -1,7 +1,10 @@
 def retry(fn, attempts: int = 3):
-    for _ in range(attempts - 1):
+    if attempts < 1:
+        raise ValueError('attempts must be >= 1')
+    last_exc = None
+    for _ in range(attempts):
         try:
             return fn()
-        except Exception:
-            pass
-    return None
+        except Exception as exc:
+            last_exc = exc
+    raise last_exc
diff --git a/test_retrying.py b/test_retrying.py
index 409b641..f752226 100644
--- a/test_retrying.py
+++ b/test_retrying.py
@@ -1,5 +1,71 @@
 from retrying import retry
 
 
+import pytest
+
+
 def test_success_first_time():
     assert retry(lambda: 5) == 5
+
+
+def test_succeeds_on_last_attempt():
+    calls = []
+
+    def fn():
+        calls.append(1)
+        if len(calls) < 3:
+            raise RuntimeError('boom')
+        return 'ok'
+
+    assert retry(fn, attempts=3) == 'ok'
+    assert len(calls) == 3
+
+
+def test_stops_calling_after_first_success():
+    calls = []
+
+    def fn():
+        calls.append(1)
+        if len(calls) < 2:
+            raise RuntimeError('boom')
+        return 'ok'
+
+    assert retry(fn, attempts=5) == 'ok'
+    assert len(calls) == 2
+
+
+def test_always_failing_calls_attempts_times_and_reraises_last():
+    calls = []
+
+    def fn():
+        calls.append(1)
+        raise ValueError(f'failure {len(calls)}')
+
+    with pytest.raises(ValueError, match='^failure 3$'):
+        retry(fn, attempts=3)
+    assert len(calls) == 3
+
+
+def test_attempts_one_calls_once_and_raises():
+    calls = []
+
+    def fn():
+        calls.append(1)
+        raise KeyError('nope')
+
+    with pytest.raises(KeyError):
+        retry(fn, attempts=1)
+    assert len(calls) == 1
+
+
+@pytest.mark.parametrize('attempts', [0, -1])
+def test_attempts_less_than_one_raises_value_error(attempts):
+    calls = []
+
+    def fn():
+        calls.append(1)
+        return 1
+
+    with pytest.raises(ValueError):
+        retry(fn, attempts=attempts)
+    assert calls == []