Skip to content

[Bug]: auto-compaction sends an empty conversation at the LIVE call site — full.length > summary.messageCount is always false there (works on resume) #878

Open

Description

@jrmvii

Summary

When --compact-threshold fires, the compacted transcript handed to the provider is [system, summary] with no live turn at all, so the model has nothing to answer and the run dies with provider returned an empty completion (no assistant text or tool call), exit 1.

Please read the call-site distinction below before testing. The tail-preservation logic in buildCompactedTranscript is correct on the resume path and unreachable on the live path. Testing with --continue will show it working.

Reproduced on main @ 8dce012, v0.1.0, built from source.

Repro

oh-my-cli --compact-threshold 1400 "read the four .txt files in this directory and report each sentinel value"

Observed, 3/3 identical runs, to the token (3 701 prompt tokens, 2 requests):

Context compacted: summarized 6 message(s) (2 completed-action receipt(s)) after 2442 prompt tokens
... 3 attempts ...
provider returned an empty completion (no assistant text or tool call)
exit 1

Negative control — the same task, same backend, without --compact-threshold: succeeds, 5 requests, 18 125 prompt tokens, all four sentinels returned correctly. So this is not the provider.

Root cause

compactMessages stamps the summary with the length of the array it was given (src/compaction.ts:258):

    messageCount: messages.length,

buildCompactedTranscript preserves the tail only when the full transcript is longer than that (src/compaction.ts:324):

  if (full.length > summary.messageCount) {
    out.push(...full.slice(summary.messageCount));
  }

That condition depends on which array full is:

  • Resume path — loadSessionMessages (src/compaction.ts:437): full is the on-disk transcript, which has grown past messageCount. Condition true, tail preserved, works as intended.

  • Live path — src/agent.ts:618-619:

          const { summary } = compactMessages(messages);
          const compacted = buildCompactedTranscript(messages, summary);

    full is the very array that produced messageCount. The condition evaluates to full.length > full.length — always false. The branch is unreachable here.

Only the live path runs during auto-compaction. So the compacted transcript is always [system, renderSummaryMessage(summary)] — and renderSummaryMessage emits role system too. The provider receives two system messages and zero user/assistant turns.

The comment directly above buildCompactedTranscript states the opposite intent — "Preserving the tail is what keeps continuation honest (Issue #719)" — which is accurate for resume and not for the live call site.

Suggested fix

Give the live path a messageCount that reflects what was summarized rather than the whole array, or have buildCompactedTranscript take the summarized slice explicitly instead of inferring it from a length comparison that only holds on one of its two callers.

Secondary observation (same flag, much smaller)

--compact-threshold can never fire on the first provider call of a process: lastPromptTokens is initialized to 0 (src/agent.ts:492) and the guard tests lastPromptTokens >= compactThreshold before the call (src/agent.ts:616). A session resumed with --continue therefore sends its full history at least once before compaction is even considered.

Verified: a 1 941-token session resumed with --compact-threshold 1500 issued a 1 984-token request with no compaction.

Context

Found while benchmarking terminal agents against a local MLX server whose usable history ceiling is ~24k tokens. oh-my-cli's own overhead is excellent for that constraint — 1 231 tokens fixed, ~237 per turn — which is why the compaction path mattered enough to chase down.

Activity

  1. added
    bugSomething isn't working
    priority:p1High priority: address before normal work
    on Aug 16, 2026
  2. github-actions commented on Aug 16, 2026

    @github-actions

    Automated intake triage

    • Type: bug
    • Priority: priority:p1
    • Confidence: 0.95

    Auto-compaction on the live call site builds a transcript with only system and summary messages, causing the provider to receive no user turn and fail with an empty completion.

    Reported evidence (unverified)

    • Observed provider returned an empty completion with exit 1 after context compaction
    • The live path passes the same messages array used to set summary.messageCount, making full.length summary.messageCount false
    • Negative control without --compact-threshold succeeded with the same task and backend

    Repository verification

    Pending. Repository verification is still required before this Issue can become agent-ready.

    This bounded pass classifies intake only. It cannot execute code, close the Issue, or grant governance authority.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpriority:p1High priority: address before normal work

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions