Skip to content

fix(agent): clamp tail-cut boundary and summary-scan indices to prevent IndexError - #75604

Closed
x7peeps wants to merge 1 commit into
NousResearch:mainfrom
x7peeps:fix/issue-75588-context-compressor-tail-cut-boundary
Closed

fix(agent): clamp tail-cut boundary and summary-scan indices to prevent IndexError#75604
x7peeps wants to merge 1 commit into
NousResearch:mainfrom
x7peeps:fix/issue-75588-context-compressor-tail-cut-boundary

Conversation

@x7peeps

@x7peeps x7peeps commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

背景

修复 #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。

修复方式

两层防御:

  1. 源头修复:_find_tail_cut_by_tokens() 的返回值用 min(n, ...) 钳制,确保永远不超过 len(messages)
  2. 防御层:_find_context_summaries() 对 start/end 参数做 [0, len(messages)] 范围钳制,即使未来调用方传入越界值也不会崩溃

验证

  • 新增 7 个回归测试,覆盖精确的越界场景
  • 全部 214 个现有 test_context_compressor.py 测试通过
  • 遵循项目 AGENTS.md 贡献规范
  • 无破坏性变更

Closes #75588

…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
@alt-glitch alt-glitch added type/bug Something isn't working P1 High — major feature broken, no workaround comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint area/compression Context compression and continuation sessions sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Jul 31, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_summary and assert it is not called and the transcript is unchanged. The new tests directly cover the helpers, but the production boundary path is agent/context_compressor.py:5074-5119.

Automated hermes-sweeper review.

messages = [
{"role": "system", "content": "sys"},
{"role": "user", "content": "u"},
]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@teknium1

teknium1 commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Merged via salvage PR #75884 (#75884) — your commits were cherry-picked onto current main with authorship preserved via rebase-merge, plus a compress()-level E2E regression test on top. Fixes #75588. Thanks for the precise two-site diagnosis and tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/compression Context compression and continuation sessions comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P1 High — major feature broken, no workaround sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Short tool-only suffix can make context compressor scan past messages

3 participants