Skip to content

Conversation

@cgwalters
Copy link

@cgwalters cgwalters commented Jan 16, 2026

What does this PR do?

Stream command output directly to a temp file when it exceeds 50KB,
avoiding memory bloat for commands with huge output. Inspired by
block/goose#2817.

New feature:

  • output_filter param: pass a regex (e.g. "^(warning|error):") to
    capture only matching lines inline while full output goes to file

The agent is informed that Grep and Read can be used to analyze the
temp files.

UI now shows filtered output stats (e.g. "Filtered: 7 matches from
2.1 KB (1.9 KB omitted)") so users can see how much output was
filtered and the total size.

Assisted-by: OpenCode (Opus 4.5)

How did you verify your code works?

Unit tests, and also by trying it out directly. (Though I only lightly tested it interactively)

@github-actions
Copy link
Contributor

Thanks for your contribution!

This PR doesn't have a linked issue. All PRs must reference an existing issue.

Please:

  1. Open an issue describing the bug/feature (if one doesn't exist)
  2. Add Fixes #<number> or Closes #<number> to this PR description

See CONTRIBUTING.md for details.

@github-actions
Copy link
Contributor

The following comment was made by an LLM, it may be inaccurate:

Potential Duplicate Found:

PR #6048 - "feat('bash'): Bash tool output saves the stdout/stderr to a file if it exceeds the limit"
#6048

This PR appears to be directly related to the current PR #8953. Both address:

  • Saving command output to a file when it exceeds a size limit
  • Preventing memory bloat from large command outputs
  • File-based output streaming

There's also a tangentially related PR:

PR #7049 - "fix: clear tool output and attachments when pruning to prevent memory leak"
#7049

This PR addresses memory management in tool output, which relates to the memory optimization goal of the current PR.

The most likely duplicate is PR #6048, which implements very similar functionality. You may want to check if it was closed, merged, or if the current PR #8953 supersedes it with additional features (like the output_filter parameter and improved GC strategy).

Assisted-by: OpenCode (Claude Sonnet 4)
Previously, output was accumulated via `output += chunk.toString()` which
creates a new string for every chunk, causing O(n²) memory usage. For
commands producing megabytes of output, this caused catastrophic memory
consumption.

Add StreamingOutput class that:
- Accumulates output in memory up to 50KB threshold
- Spills to a temp file when threshold exceeded
- Tracks bytes incrementally to avoid scanning

Fixes memory exhaustion on commands like `find /` or large builds.

Assisted-by: OpenCode (Claude Sonnet 4)
Add output_filter parameter that captures regex-matching lines inline
while streaming full output to file. Useful for build commands where only
warnings/errors matter.

When a filter is provided:
- Full output still streams to temp file (for later inspection)
- Only matching lines are returned inline to the LLM
- Filter stats (matchCount, filteredBytes) are tracked incrementally

Example usage:
  output_filter: "^(warning|error|WARN|ERROR):"

This avoids the LLM having to grep through large build outputs to find
the relevant diagnostics.

Assisted-by: OpenCode (Claude Sonnet 4)
@cgwalters
Copy link
Author

I'll be very clear in this that I have not written much Typescript and this was basically 100% output from Opus 4.5, but I did (as I do with all code targeted for important projects) review it at least superficially and I tested this a good bit.

I've now also rolled in the changes from #9693 but this ensures that the O(n^2) problem is fixed for both the inline ! verb and the bash tool.

There's a separate commit for the filtering which is more of a feature than a bugfix - and I think this is somewhat novel in that while many tools similar to OpenCode offer a bash/shell tool, this "feature" one is one I only tried to add to goose.

One thing I notice related to this though is at least Opus 4.5 habitually does e.g. make | tail -100 and such - it's been trained to avoid large output, but IMO this filtering functionality is a lot better because it ensures it can still access all of the data if it needs to.

@cgwalters
Copy link
Author

While it's not new, I really dislike how opencode writes command output into $HOME by default...personally what I think would be better at least on Unix derivatives is having unlinked (i.e. anonymous) tempfiles so they reliably get GC'd when the process exits among other things. We'd just need to teach e.g. the grep tool to operate on an in-memory file descriptor too.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant