fix(agent): clamp tail-cut boundary and summary-scan indices to prevent IndexError - #75604
Conversation
…nt IndexError Fix NousResearch#75588 ## Root cause When a short conversation ends in a tool-call/result group and the protected head alignment reaches the end of the message list, _find_tail_cut_by_tokens() could return len(messages) + 1. This happened because the final return used max(cut_idx, head_end + 1) which could push past the array length when head_end >= len(messages). The out-of-range value then propagated into _find_context_summaries() which iterated range(start, end) and indexed messages[idx] without clamping, raising IndexError and failing the active gateway turn. ## Fix Two-layer defense: 1. Source fix: _find_tail_cut_by_tokens() now clamps its return to min(n, ...) so it never exceeds len(messages). 2. Defensive clamp: _find_context_summaries() now bounds start/end to [0, len(messages)] so even if a future caller passes bad values, it cannot crash. ## Verification - 7 new regression tests for the exact boundary conditions - All 214 existing test_context_compressor.py tests pass
teknium1
left a comment
There was a problem hiding this comment.
Thanks for the focused boundary fix. Current main still has the invalid tail-cut return: agent/context_compressor.py:4940 can return len(messages) + 1 when head_end == len(messages), and _align_boundary_forward() preserves that out-of-range value (agent/context_compressor.py:4408-4410). The proposed min(n, ...) clamp makes the existing no-compressible-window guard in compress() (agent/context_compressor.py:5095-5119) handle that state correctly.
Suggested changes
- Add an end-to-end
compress()regression for the aligned-head-at-end shape. Patch_generate_summaryand assert it is not called and the transcript is unchanged. The new tests directly cover the helpers, but the production boundary path isagent/context_compressor.py:5074-5119.
Automated hermes-sweeper review.
| messages = [ | ||
| {"role": "system", "content": "sys"}, | ||
| {"role": "user", "content": "u"}, | ||
| ] |
There was a problem hiding this comment.
Please add a compress()-level regression for this aligned-head-at-end transcript, asserting _generate_summary is not called and the input is returned unchanged. This direct helper assertion does not exercise the production compress_start >= compress_end no-op branch.
背景
修复 #75588: 当短对话以 tool-call/result 组结尾且受保护的 head 对齐到消息列表末尾时,_find_tail_cut_by_tokens() 会返回 len(messages)+1,导致 _find_context_summaries() 在索引 messages[idx] 时触发 IndexError,直接中断 gateway 活跃轮次。
根因分析
当 head_end >= len(messages) 时,_find_tail_cut_by_tokens() 末尾的 return 语句使用 max(cut_idx, head_end + 1) 会将索引推到 len(messages)+1。这个越界值随后传入 _find_context_summaries(),后者在 range(start, end) 中迭代并直接索引 messages[idx],未做任何范围钳制,最终抛出 IndexError。
修复方式
两层防御:
验证
Closes #75588