Skip to content

Compaction can reuse an oversized request and leave only 1024 output tokens #62943

Open

Description

@linus-ahlemeyer

Description

Compaction can reuse a request that its measured token count already ruled out, leaving only 1024 output tokens for the summary.

With no retained tail, withoutTail() returns the original request. deliver() rejects it using measured usage, then accepts the same object using a lower character estimate. fitOutput() still uses the measurement, so it cuts the summary's output allowance instead of using the existing text fallback.

OpenCode version

Observed on 2.2.5; reproduced on dev at 12a661ea40cd5863b271a89bb06b8a3ddb7ab5a0.

Steps to reproduce

Add this case beside the existing summaryFixture tests in packages/core/test/session-compaction.test.ts:

it.effect("summary keeps output room despite a low character estimate", () =>
  Effect.gen(function* () {
    const fixture = yield* summaryFixture([
      textUser("Latest question"),
      measured("Latest answer", summaryWindow - 1024),
    ])
    expect(yield* fixture.run()).toEqual({ status: "completed" })
    expect(requests[0].maxTokens).toBe(summaryBuffer)
  }),
)

Run bun run script/test.ts test/session-compaction.test.ts from packages/core.

Expected: the oversized measured request takes the text fallback, leaving the fixture's 25,000-token summary allowance. Actual: maxTokens is 1024. The synthetic usage count makes the disagreement deterministic without a live provider.

Related: #59951 also addresses this case within a broader context-budget rewrite.

Operating System

Ubuntu 24.04.4 LTS, Bun 1.4.4.

Activity

  1. opencodejr commented on Nov 9, 2026

    @opencodejr

    Thanks for the precise report. I reproduced this with your unit test on the latest dev (7524e5f). Compaction completes, but the summary request is sent with maxTokens = 1024 instead of the fixture's 25000.

    On the v2.2.5 tag the same test can't show it: the test model there has no output limit, so maxTokens is undefined. The code involved is the same in that release.

    The cause is as you describe. With no retained tail, withoutTail() returns prepared.request itself. In deliver(), that request fails the measured check, then passes the character-estimate check as shrink.history. Because it is the same object, the measured size still reaches fitOutput(), which cuts the output allowance instead of taking the text fallback.

    The script below adds your test, runs it against a checkout and restores the file. Related: #62944 (targeted fix) and #59951 (broader context-budget rewrite).

    Reproduction script (repro.sh)
    #!/usr/bin/env bash
    # Reproduces #62943: compaction reuses an oversized measured request and cuts the
    # summary's output allowance to 1024 tokens instead of taking the text fallback.
    # Usage: repro.sh <path to opencode checkout> (dependencies installed with `bun install`)
    set -euo pipefail
    REPO="${1:-/workspace/repo}"
    cd "$REPO/packages/core"
    TEST=test/session-compaction.test.ts
    cp "$TEST" /tmp/session-compaction.test.ts.bak
    trap 'cp /tmp/session-compaction.test.ts.bak "$TEST"' EXIT
    cat >> "$TEST" <<'EOF'
    
    it.effect("repro #62943: summary keeps output room despite a low character estimate", () =>
      Effect.gen(function* () {
        const fixture = yield* summaryFixture([
          textUser("Latest question"),
          measured("Latest answer", summaryWindow - 1024),
        ])
        expect(yield* fixture.run()).toEqual({ status: "completed" })
        console.log("REPRO maxTokens =", requests[0].maxTokens, "expected summaryBuffer =", summaryBuffer)
        expect(requests[0].maxTokens).toBe(summaryBuffer)
      }),
    )
    EOF
    bun run script/test.ts "$TEST" -t "repro #62943" 2>&1 | grep -E "REPRO|pass|fail|Expected|Received|error" | head -20

    Run it: bash repro.sh

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions