Skip to content

OpenCode serve memory grows without bound: MCP server processes are never (or incorrectly) reclaimed #55017

Closed as duplicate

Description

@iWorld-y

OpenCode serve memory grows without bound: MCP server processes are never (or incorrectly) reclaimed

Summary

OpenCode serve retains a full set of MCP server child processes for every project directory that was ever touched, and never reclaims them. In one 2.5-hour session of normal use (one or two projects actively used at a time), the serve process tree reached about 9 GB RSS, about 8.3 GB of which was MCP server child processes.

Two defects behind the growth:

1. Locations are never evicted, so their MCP fleets stay resident. The location graph cache is built with idleTimeToLive: Duration.infinity, and the LocationActivity sweep that is supposed to reclaim idle locations rarely evicts in practice.

2. Eviction leaks processes. When a location is evicted, most of its MCP children are killed, but some survive as orphans (PPID 1) and live forever.

Restarting the server releases everything; memory climbs back as more directories are opened.

Environment

  • macOS 26, Apple Silicon
  • opencode v2.0.26 / v2.0.27
  • opencode serve managed by the OpenChamber desktop app; the same setup applies to any long-running serve, e.g. opencode serve --service
  • Global opencode.json with 7 local stdio MCP servers: chrome-devtools-mcp, @playwright/mcp, @toolbox-sdk/server --prebuilt=mysql, codegraph, gitea-mcp, mcp-server-tapd, and an Open Design MCP server

With 7 configured MCP servers, one location's fleet is about 1 GB RSS (the @toolbox-sdk/server --prebuilt=mysql entry alone spawns 4 processes, about 550 MB). Visiting 8 directories accumulates about 8 GB.

Root cause

1. Location graphs never expire

packages/core/src/location-services.ts:56:

// Retain healthy graphs. Boot failures, not local filesystem probes,
// decide whether a location (including workspace placement) can retry.
{ idleTimeToLive: Duration.infinity },

Reclaiming a location's graph depends entirely on the LocationActivity sweep (packages/core/src/location-activity.ts, default TTL 60 minutes, sweep interval 1 minute). The sweep's conditions make eviction rare:

  • Only SessionEvent.Durable touches a location's activity timestamp. Booting a directory, connecting its MCP servers, and other work never counts as activity.
  • Before evicting, the sweep checks the location for active sessions (execution.active); any active session re-touches the entry.
  • With idleTimeToLive: Duration.infinity, there is no backstop: a location that slips past the sweep's conditions is cached forever.

The code comment at packages/web (OpenChamber side) confirms the cost model: "the first directory-scoped read boots that location's whole MCP fleet". Every directory a user merely opens pays a full fleet, and nothing takes it back.

2. Eviction leaks MCP child processes

packages/core/src/mcp/stdio.ts closes the transport through a scope finalizer that signals the child process (SIGTERM, then SIGKILL after a grace period). In practice, eviction killed most of the fleet but leaked at least one process: a codegraph serve --mcp --path <storymaker> child survived its location's eviction with PPID 1 and is still running. Its fleet siblings from the same boot were all terminated correctly.

Evidence

From one running serve, aligned against ~/.local/share/opencode/log/opencode.log (times in UTC):

  • 14 distinct directories booted during the day. Each boot spawns that location's full MCP fleet (7 server kinds; mysql toolbox is 4 processes).
  • 8 fleets remain attached to the running serve (about 80 child processes, 8.3 GB RSS), matching 6 still-cached locations, most of them idle for hours.
  • Eviction does work sometimes: /Users/jty/bin was evicted on the expected 60-minute cycle several times. Many other locations were never evicted at all.
  • Orphaned process after eviction: codegraph serve --mcp --path /Users/jty/project/storymaker (pid 18938) was re-parented to PID 1 when the storymaker location was evicted at 03:30:21Z. Every other process from that fleet died as expected.
  • Separate issue observed in the same log: the Open Design MCP server exits by itself with code 0 while idle, and OpenCode immediately reconnects. The log accumulated 1,098 MCP server process exited events, a spawn loop at a rate of one exit every 1 to 3 minutes.

Impact

  • Memory grows linearly with the number of directories visited, with no cap. Restarting serve is the only reliable way to release it.
  • mysql toolbox is the heaviest single item: 4 processes and about 550 MB per fleet.
  • MCP servers that carry credentials in argv (gitea-mcp in our config) keep their plaintext tokens visible in ps output for as long as the processes live. The longer the processes survive, the larger the exposure.

Expected behavior

  1. Idle locations get reclaimed: a finite idleTimeToLive for the location graph cache, or a broader activity definition in LocationActivity (no active sessions and no recent activity expires the location). Either way, an idle location's MCP fleet should shut down and can be re-spawned on the next request.
  2. Eviction kills the whole tree: when a location closes, all of its MCP child processes, including grandchildren, are terminated.
  3. Optional: reconnect backoff for MCP servers that exit repeatedly, to stop the per-minute respawn loop.

Note for OpenChamber

OpenChamber manages only the opencode serve process itself; the MCP children are not in its registry, so it cannot reclaim them downstream. The fix belongs here. OpenChamber may separately want to surface MCP child memory usage and offer a manual cleanup control.

Activity

  1. opencode-agent commented on Oct 13, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed analysis and measurements. I think this is a duplicate of #51003, which reports the same main problem: a long-running opencode serve (OpenChamber in that report too) starts a full set of global stdio MCP servers for every directory it loads, and never releases them, so memory grows until the host runs out.

    Possible duplicates and closely related issues, ordered from most to least likely:

    The last point in your report, where the Open Design server exits with code 0 while idle and is respawned every 1–3 minutes, doesn't seem to be covered by an existing issue. A separate issue for it (ideally with the server config and a log excerpt) would be welcome.

    The bot will close this issue as a duplicate in 1 day unless you reply explaining how your problem is different. Adding your v2.0.26/2.0.27 data to #51003 would also be useful there.

  2. opencode-agent commented on Oct 14, 2026

    @opencode-agent
    Contributor

    Closing this as a duplicate of #51003, since there's been no reply saying how this differs. That issue covers the same problem: a long-running opencode serve starts a full set of global stdio MCP servers for every directory it loads, never releases them, and memory grows until the host runs out. PR #53046 is open against it. Your v2.0.26/2.0.27 measurements would be useful there.

    For the other parts of your report:

    If this turns out to be a different problem, reply here and it can be reopened.

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