Skip to content

fix: preserve first user message to guarantee API validity - #196

Merged
ranxianglei merged 2 commits into
masterfrom
2026-07-25_preserve-first-user-msg
Jul 25, 2026
Merged

fix: preserve first user message to guarantee API validity#196
ranxianglei merged 2 commits into
masterfrom
2026-07-25_preserve-first-user-msg

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

Summary

Fixes the month-long session-freeze bug (zhipuai-lb code 1214, "messages 参数非法") where compression leaves zero user-role messages in the request.

Root cause

The v1.13.2 preserve-last-user fix (lib/messages/prune.ts) searched the messages array for a pruned user message to restore when filtering would leave zero users. After OpenCode compaction removes pruned messages from the array, the search finds nothing — zero-user requests slip through and the provider hard-rejects.

Reproduced in ses_0b89319b1ffeK25eKU3GMfCK8U: pre-compress 128 msgs / 12 user → post-compress 11 msgs / 0 user → API 400 code 1214 → session frozen.

Fix

Replace preserve-last-user with preserve-first-user: the first user message (the session's original task, always present in the array) is unconditionally force-preserved (survive = true), regardless of prune state. This does not depend on the integrity of the pruned portion of the array.

const firstUserIdx = messages.findIndex((msg) => msg.info.role === "user")
if (firstUserIdx >= 0) {
    survive[firstUserIdx] = true
}

Trade-off

When the first user is compressed and a later user also survives, two adjacent user messages can appear (e.g., [u1, u2, a2]). This is an accepted trade-off:

  • Zero user messages → hard API rejection (code 1214). MUST prevent.
  • Adjacent user messages → accepted by virtually all providers (OpenAI, Anthropic, Google, zhipuai-lb).

The Bug 36 e2e test was updated: the force-preserved first user is exempted from the adjacent-user check. The no-adjacent-users invariant still holds for all non-first-user pairs.

Files

  • lib/messages/prune.ts — preserve-first-user logic (replaces preserve-last-user)
  • tests/prune.test.ts — 5 tests rewritten for new invariant
  • tests/e2e-message-transform.test.ts — 3 e2e tests updated

Verification

  • npm run typecheck — clean
  • npm test846 pass, 0 fail
  • npm run build — clean

Ultraworked with Sisyphus

ranxianglei and others added 2 commits July 25, 2026 15:53
The v1.13.2 preserve-last-user fix searched the messages array for a pruned
user message to restore when filtering would leave zero user-role messages.
After OpenCode compaction removes pruned messages from the array, the search
finds nothing and zero-user requests hit zhipuai-lb code 1214 (session freeze).

Replace with preserve-first-user: the first user message (session's original
task, always present in the array) is unconditionally force-preserved
(survive=true), regardless of prune state. This does not depend on the
integrity of the pruned portion of the array.

Trade-off: when the first user is compressed and a later user also survives,
two adjacent user messages can appear in the output. Zero-user is a hard API
rejection; adjacent users are accepted by virtually all providers.

Files: lib/messages/prune.ts
Tests: tests/prune.test.ts (5 tests rewritten for new invariant)

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Three e2e tests in message-transform pipeline asserted u1 (first user) is
pruned when it falls in a compression range. Under preserve-first-user, u1
is always force-preserved. Update assertions accordingly.

Bug 36 (no adjacent users) test: the force-preserved first user is exempted
from the adjacent-user check — zero-user is a hard API rejection while
adjacent users are accepted by virtually all providers.

Tests: tests/e2e-message-transform.test.ts

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@ranxianglei
ranxianglei merged commit 79c7173 into master Jul 25, 2026
4 checks passed
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