Skip to content

core: session shell output bypasses tool limits and strands sessions #45099

Closed

Description

@kitlangton

Summary

In OpenCode V2, shell commands executed directly through a session can persist approximately 1 MiB of output and inject it into the next model request without applying the normal 50 KiB tool-output limit. The oversized request can exceed the model context window, fail as provider.unknown, and leave the session unable to continue automatically.

Environment

  • opencode version: opencode2 v0.0.0-beta-18230
  • OS: macOS Darwin 25.5.0, arm64
  • Terminal: Ghostty (TERM=xterm-256color, COLORTERM=truecolor)
  • Shell: /bin/zsh
  • Install/channel: V2 beta
  • Active plugins: Not relevant; reproducible through the built-in session shell path.

Reproduction

  1. Start a V2 session with a model whose remaining context budget is smaller than approximately 1 MiB of shell output.
  2. Execute a user shell command through the session that writes more than 1 MiB, such as a failing typecheck with extensive diagnostics.
  3. Submit another prompt so OpenCode assembles a model request containing the persisted shell message.
  4. Observe the provider reject the oversized request and the session fail instead of recovering automatically.

Expected Behavior

Session shell output should obey the same bounded preview behavior as normal shell tool output. The model should receive a compact preview, with full output retained separately when appropriate, and the session should remain usable.

Actual Behavior

  • Normal tool output defaults to 50 KiB in packages/core/src/tool-output.ts.
  • The session shell path captures up to 1 MiB through SHELL_MAX_CAPTURE_BYTES in packages/core/src/session.ts.
  • packages/core/src/session/runner/to-llm-message.ts inserts the entire persisted shell output directly into the model-facing message.
  • Automatic compaction considers token usage reported by the preceding model response, which does not include subsequently added shell output.
  • The resulting provider failure can surface as provider.unknown, bypassing context-overflow recovery.

Additional Context

Observed in a production session where a bun typecheck shell command persisted 1,048,701 characters despite output metadata reporting truncated: true. The session required manual compaction to recover.

The smallest likely fix is to apply the existing 50 KiB tool-output cap to session shell capture, rather than introducing request-wide token estimation solely to compensate for this inconsistent limit. Regression coverage should verify that a large session shell command cannot create an oversized model-facing shell message.

Activity

  1. added
    bugSomething isn't working
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on Aug 26, 2026
  2. argszero commented on Aug 26, 2026

    @argszero
    Contributor

    I'd like to work on this. The suggested minimal fix — applying the existing 50 KiB tool-output cap from packages/core/src/tool-output.ts to session shell capture in packages/core/src/session.ts (SHELL_MAX_CAPTURE_BYTES), so to-llm-message.ts never inserts an oversized shell message — matches the code paths described here. I'll add regression coverage verifying a large session shell command cannot produce an oversized model-facing message.

  3. argszero commented on Aug 26, 2026

    @argszero
    Contributor

    Standing down — I see PR #45136 (and #45109) already address this. I'll defer to those.

  4. lab1207 commented on Sep 29, 2026

    @lab1207

    Taking this one - opened fix in #52024. Note: on current master the capture-side names from the issue are gone and V2 shell is unimplemented, so I bounded the model-facing interpolation (50 KiB preview, history intact) with a regression test.

  5. xxwtiancai commented on Oct 25, 2026

    @xxwtiancai

    Confirmed on current dev (e0b6039e1): the session shell path still has no bounded preview. packages/core/src/session/shell.ts captures up to 1 MiB (shell.output(info.id, { limit: 1024 * 1024 })), ShellResult.userNotification embeds the full captured output in the synthetic message, and both replay paths in to-llm-message.ts (synthetic and shell cases) insert it whole — the 50 KiB cap from tool-output.ts never applies here.

    The previous attempts all fell to process, not review: #45136/#45942 to the guideline window, #50660 to cleanup, and #51890 only because it targeted the retiring v2 branch. If nobody is on it, I'd like to take this. Planned shape: keep the 1 MiB capture for display, but bound what reaches the model (at synthetic-message construction or replay) to match tool-output behavior, with regression coverage in session-shell.test.ts.

  6. rekram1-node commented on Oct 26, 2026

    @rekram1-node
    Collaborator

    This is fixed on dev by ad9117b (fix(session): run user shells immediately in the background).

    Output from shell commands you run yourself in a session now goes through the same limit as the shell tool (2,000 lines / 50 KiB, configurable via tool_output) before the model sees it. The rest is saved to a file and the model gets a pointer to it.

    The 1 MiB capture is still there, but it's only the copy shown in the UI. That message is marked as background and isn't sent to the model, so seeing 1 MiB in the UI doesn't mean the model got it. If you still see oversized shell output reaching the model on a current build, let me know and I'll reopen.

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

    2.0bugSomething isn't workingcoreAnything pertaining to core functionality of the application (opencode server stuff)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions