anomalyco / anomalyco/opencode

service: managed serve stays inside the spawning client's cgroup — client memory limits starve the shared server into watchdog kill loops

Open
#48,588 1 comment 0 reactions 1 assignee View on GitHub

@kitlangton is already working on this.

Since Sep 12, 2026.

2.0
Dominant language
TypeScript
Stars
209k
Forks
27.5k
PR merge metrics
PR metrics pending

Description

Summary

The managed serve --service process is spawned detached: true by whichever client's ensure() wins the contender race, but detaching does not move it out of the client's cgroup. On Linux, when the client runs inside a resource-limited scope (systemd scope, container, etc.), the shared server inherits — and is billed against — the client's memory/CPU limits for its entire lifetime, outliving that client and serving every other attached session.

When the cgroup crosses memory.high, the kernel forces synchronous direct reclaim on all processes in the group. The serve stalls long enough to fail the /api/health probe (2s requestTimeout, 3 consecutive timeouts in packages/client/src/effect/service.ts), after which the client calls terminate() — SIGTERM on the shared server every session depends on. All clients then race to spawn contenders; the winner lands in another pressured cgroup, and the loop repeats every ~60–90s indefinitely.

Observed

  • 4 TUI clients sharing one serve --service. The serve was replaced every ~60–90s; the log shows repeated Background service is unresponsive; recovery cannot preserve persistent terminals followed by concurrent background service starting from several clients within ~300ms.
  • memory.events on the client scopes: high counter at 357k–628k crossings; oom_kill 0 — pure reclaim stalls, not OOM kills.
  • Raising the scope's memory.high from 3G to 8G stopped the flap immediately: the same serve survived a 4.76GB peak (above the old hard cap) and has been stable since.
  • Reproduces identically on 0.0.0-beta-19425, -19500, and -19507 — not a version regression. The serve's footprint itself is large; #47727's undisposed per-request instances/MCP children likely contribute to crossing the limit.

Environment

  • opencode 0.0.0-beta-19500 / -19507 / -19425, npm:@opencode/cli via mise
  • NixOS, kernel 6.12, systemd user scopes (systemd-run --user --scope, MemoryHigh=3G)
  • 4 concurrent TUI clients; ~6GB opencode.db; several MCP servers

Suggestions

  • Spawn the managed serve outside the client's cgroup when a mechanism is available (e.g. systemd-run --user --scope on Linux), and/or
  • Make the health watchdog tolerant of reclaim stalls: longer requestTimeout / backoff during startup, or verify process liveness separately from HTTP latency, and/or
  • Document that resource limits on the launching client apply to the shared server.

Related: #37795 (same fragile 2s probe on service restart), #47727 (serve memory growth from undisposed instances), #42270 (stale daemons after restart).

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.