Skip to content

fix: split protected ranges + soften last-user-message protection - #210

Merged
ranxianglei merged 2 commits into
masterfrom
2026-07-27_split-protected-ranges
Jul 27, 2026
Merged

ranxianglei merged 2 commits into
masterfrom
2026-07-27_split-protected-ranges

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

Summary

Fixes a bug where autonomous agentic sessions (1 user message + many assistant/tool messages) could never successfully compress. Root cause: buildCompressibleRanges created one giant group (no user-message breaks since tool results are assistant role), and excludeProtectedRanges removed the entire group because its endRef was in the protected zone — leaving zero recommendations and suppressing the nudge.

Two changes:

  1. Group splitting (buildCompressibleRanges): now accepts protectedZoneRefs and splits groups at the protected-zone boundary. The unprotected head survives as a recommended range; the protected tail is excluded. This directly fixes the "can't compress in autonomous sessions" bug.

  2. Last-user-message softening (preserveLastUserMessage): moved from hard-reject (checkProtectedRange throw) to soft-filter (filterLastUserMessage excludes from compress plan, following Bug 39's filterProtectedToolMessages pattern). The last user message survives in visible context; surrounding tool output compresses normally. No more hard rejection when the compress range happens to include the last user message.

Also added allInProtectedZone condition to nothingToCompress to cover the new case where all messages are in the protected zone (group splitting produces zero compressible ranges — previously this would incorrectly fire the nudge with no recommendations).

Test plan

  • 922/922 tests pass (920 existing + 2 new)
  • Typecheck clean
  • Build clean
  • New test: giant group (1 user + 49 assistant) splits at protected boundary
  • New test: all messages in protected zone → no compressible ranges
  • Updated test: last user message soft-filtered (not hard-rejected)
  • Updated test: computeProtectedRefs no longer includes last user message

In autonomous agentic sessions (1 user message + many assistant messages),
buildCompressibleRanges created one giant group because grouping only breaks
on user messages — and tool results are assistant role in OpenCode. The giant
group's endRef fell in the protected zone → excludeProtectedRanges removed
the entire range → no recommendations → nudge suppressed → model could never
compress.

Two changes:

1. Group splitting: buildCompressibleRanges now accepts protectedZoneRefs and
   splits groups at the protected-zone boundary. The unprotected head survives
   as a recommended range; the protected tail is excluded.

2. Last-user-message softening: preserveLastUserMessage moved from hard-reject
   (checkProtectedRange throw) to soft-filter (filterLastUserMessage excludes
   from compress plan, like Bug 39 protected tools). The last user message
   survives in visible context; surrounding tool output compresses normally.

Also added allInProtectedZone condition to nothingToCompress to cover the new
case where all messages are in the protected zone (group splitting produces
zero compressible ranges).

922 tests pass (920 existing + 2 new).
Review findings (both agents converged):
1. pipeline.ts checkProtectedRange error/JSDoc mentioned 'most recent
   user message' but that rule moved to filterLastUserMessage. Removed
   stale clause.
2. range.ts empty-plans error said 'protected tool outputs' exclusively
   but cause could also be last-user-message filtering. Updated message.
3. message.ts had weak guard (filteredPlans.length === 0 && skippedCount > 0)
   that missed the case where all plans soft-filtered with skippedCount=0.
   Now throws unconditionally when filteredPlans is empty, with accurate
   message branching on skippedCount.
4. Updated test assertion in protected-tool-exclusion.test.ts for new
   error string.

922/922 tests pass.
@ranxianglei
ranxianglei merged commit 5b91c3e into master Jul 27, 2026
5 checks passed
ranxianglei added a commit that referenced this pull request Jul 27, 2026
E2E tests had severe gaps that made them ineffective:
- ALL protection disabled (preserveRecentMessages:0) — the mechanism
  fixed 3 times (PR #210/#211/#212) was never exercised
- CI ran only 4/6 scenarios (05 subagent, 06 nudge missing)
- verify.ts only checked blockCount, not which messages were protected

Changes:
- run-e2e.sh: per-scenario config override via 'acpConfig' JSON field
- verify.ts: compressedCount/min/max checks (byMessageId inspection)
- scenario 07: protection-filtered (production config, compress succeeds
  via soft-filter, protected msgs excluded from compressed set)
- scenario 08: nudge-with-protection (nudge→compress with protection
  enabled — tests exact bug from PR #207/#210/#212)
- ci.yml: all 8 scenarios now in CI

All 8 scenarios verified locally (8/8 pass).
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