Skip to content

fix: summarization retries panicked in debug, and a test relied on it - #156

Merged
yuanhao merged 1 commit into
mainfrom
fix/retry-attempt-underflow
Aug 23, 2026
Merged

fix: summarization retries panicked in debug, and a test relied on it#156
yuanhao merged 1 commit into
mainfrom
fix/retry-attempt-underflow

Conversation

@yuanhao

@yuanhao yuanhao commented Aug 23, 2026

Copy link
Copy Markdown
Collaborator

Found by the test-coverage review of #155, outside the question it was asked.

The bug

RetryConfig::delay_for_attempt documents a 1-indexed attempt and computes attempt - 1. llm_compaction.rs passed the raw 0..=max_retries loop variable, so the first retry underflowed usize and panicked in debug builds. agent_loop.rs increments before calling and was correct — this was confined to compaction.

The code even writes attempt + 1 for its own log message a few lines above, so the 0-indexing was known locally and lost at the call.

It landed on a detached task, so nothing surfaced. The summarization simply vanished, no briefing arrived, and compaction fell back deterministically — which is one of the behaviours #150 was filed about. Verified directly:

thread '...' panicked at src/retry.rs:47:73: attempt to subtract with overflow

delay_for_attempt now uses saturating_sub, so a caller that misses the 1-indexed contract loses the backoff rather than the task. Both call sites are corrected to pass attempt + 1.

A test was passing because of the panic

provider_failure_falls_back_deterministically asserts that a failed request leaves the in-flight slot idle, and checks after 100ms. Its FailingProvider returns a retryable error — so the instant panic-death released the slot well inside that window.

With retries actually working, the first backoff is ~1s and the slot is legitimately still held, so the test failed. It now configures RetryConfig::none(): it is about the drop guard, not about backoff timing. The reason is written into the test so it isn't "fixed" back later.

Coverage hole from #155 that I had missed

Deleting the only warn_losing_race_once() call site in compact() disconnects the entire feature — the counter never moves for any real user — and every test stayed green, because they all call the function directly. compact_counts_real_fallbacks_and_warns_on_the_streak now drives the real path through compact(); deleting the call site fails it.

I should flag that I briefly reported this mutation as already caught. It wasn't — the suite was red at the time from the retry fix above, and I read a pre-existing failure as a catch. Re-verified against a green baseline before and after.

Verification

mutation result
restore (attempt - 1) attempt to subtract with overflow
delete the warn_losing_race_once() call site each deterministic compaction must advance the streak
delete the discard-path counter must advance the streak, not clear it

cargo test --all-features: 627 passed, 0 failed. Clippy clean under -Dwarnings.

🤖 Generated with Claude Code

Found by the test-coverage review, outside the question it was asked.

RetryConfig::delay_for_attempt documents a 1-indexed attempt and computes
`attempt - 1`. llm_compaction.rs passed the raw `0..=max_retries` loop
variable, so the first retry underflowed usize and panicked in debug.
agent_loop.rs increments before calling and was correct; this was
confined to compaction. The code even wrote `attempt + 1` for its own log
message, so the 0-indexing was known locally and lost at the call.

The panic landed on a detached task, so nothing surfaced: the
summarization vanished, no briefing arrived, and compaction fell back
deterministically — one of the behaviours #150 is about. delay_for_attempt
now saturates, so missing the contract costs the backoff, not the task.

provider_failure_falls_back_deterministically was passing *because* of the
panic. FailingProvider returns a retryable error; the instant death
released the in-flight slot well inside the test's 100ms window. With
retries working the first backoff is ~1s and the slot is legitimately
still held, so the test now configures no retries — it is about the drop
guard, not backoff timing.

Also closes the coverage hole the review found and I had missed: deleting
the only warn_losing_race_once call site in compact() disconnected the
whole feature — the counter would never move for any real user — and every
test stayed green, because they all call the function directly.
compact_counts_real_fallbacks_and_warns_on_the_streak drives the real
path; deleting the call site now fails it.

I briefly believed that mutation was already caught. It was not — the
suite was red at the time from the retry fix above, and I misread a
pre-existing failure as a catch. Re-checked against a green baseline.

627 passed, clippy clean under -Dwarnings.

Refs #150

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@yuanhao
yuanhao merged commit a688396 into main Aug 23, 2026
8 checks passed
@yuanhao
yuanhao deleted the fix/retry-attempt-underflow branch August 23, 2026 09:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant