Skip to content

Memory stays unbounded: ACP session map + compacted tool outputs #4315

Closed

Description

@riatzukiza

Description

Two separate memory leaks keep OpenCode RSS growing without bound:

  1. ACP session manager never frees entries. ACPSessionManager (packages/opencode/src/acp/session.ts:9-72) pushes every new ACP session into a private Map and there is no delete or lifecycle hook tied to cancel. Each ACP IDE reconnect adds another entry plus async subscriptions from setupEventSubscriptions (acp/agent.ts:62-335). The repro script packages/opencode/script/memory/acp-session-leak.ts shows RSS climbing from ~80 MB to ~96 MB after 20k synthetic sessions even with forced GC; heap snapshots (logs/acp-session-leak-*.heapsnapshot) retain all session objects.

  2. Tool compaction does not clear payloads. SessionCompaction.prune only stamps part.state.time.compacted, but the underlying state.output / attachments stay in storage. Every SessionPrompt.prompt reloads the full tool history via MessageV2.stream()/toModelMessage, so sessions with thousands of big tool outputs are re-materialized on every turn. Users report >10 GB RSS after a day because "cleared" tool results are still parsed into JS objects.

Both leaks are captured in spec/memory-leaks.md (benchmark table + repro steps). We need lifecycle cleanup for ACP sessions/subscriptions and real data eviction when compaction marks tool outputs as old.

OpenCode version

v1.0.65

Steps to reproduce

  1. ACP leak: NODE_OPTIONS=--expose-gc npx tsx packages/opencode/script/memory/acp-session-leak.ts → observe RSS + session map size continuing to grow; inspect logs/acp-session-leak-*.heapsnapshot in Chrome DevTools.
  2. Tool-output leak: run a long chat session that repeatedly invokes read/edit/bash on large files (or script fake tool parts) and watch process.memoryUsage() / heap snapshots. Even after compaction UI shows "Old tool result content cleared", the backing JSON still contains every output string which gets reloaded on each prompt.

Screenshot and/or share link

N/A

Operating System

Linux (container)

Terminal

tmux + bash inside Codex CLI

Activity

  1. riatzukiza commented on Nov 14, 2025

    @riatzukiza
    ContributorAuthor

    Adding direct links to the repro scripts mentioned above for convenience:

    1. ACP session leak harness: https://gist.github.com/riatzukiza/5d64e219d80db584132d638102e1f03a

      • Run with NODE_OPTIONS=--expose-gc npx tsx acp-session-leak.ts from the repo root (or copy into packages/opencode/script/memory).
      • Logs RSS / heap every 1k synthetic ACP sessions and writes a heap snapshot under logs/ so you can inspect retained session entries in Chrome DevTools.
    2. Tool-output retention stress test: https://gist.github.com/riatzukiza/d40f37809f3cf4141ba2ba5d3360a984

      • Run with node --expose-gc tool-output-leak.ts (env vars: ITERATIONS, PARTS_PER_ITERATION, OUTPUT_KB, CLEAR_COMPACTED=true, SNAPSHOT_NAME=foo.heapsnapshot).
      • Mimics MessageV2.stream() repeatedly loading compacted tool parts by keeping all historical outputs in memory; toggle CLEAR_COMPACTED to see how clearing state.output stabilizes RSS.

    Feel free to make the gists public or pull them into the repo if that’s easier for others to run.

  2. riatzukiza commented on Nov 14, 2025

    @riatzukiza
    ContributorAuthor
  3. rekram1-node commented on Nov 14, 2025

    @rekram1-node
    Contributor

    The acp one isn't that big of an issue tbh each session is pretty minimal but yeah if the protocol has some way of handling a closed connection I should drop the data for it too

  4. self-assigned this
    on Nov 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions