#16730 · @BYK · opened Mar 9, 2026 at 10:52 AM UTC · last updated Mar 21, 2026 at 10:24 AM UTC
fix(opencode): reduce memory usage and database bloat in long-running instances
Score breakdown
Impact
Clarity
Urgency
Ease Of Review
Guidelines
Readiness
Size
Trust
Traction
Summary
This PR addresses critical memory usage and database bloat in long-running OpenCode instances. It implements several fixes including database health improvements, optimized output spooling, data compaction, algorithm optimizations, and memory leak resolutions.
Description
Issue for this PR
Closes #16777
Type of change
- [x] Bug fix
- [ ] New feature
- [x] Refactor / code improvement
- [ ] Documentation
What does this PR do?
A long-running OpenCode server (2+ days) was consuming 1.76 GB (602 MB RSS + 1.15 GB swap) on an 8 GB system. The database grew to 1.99 GB (274K parts, 1,706 sessions spanning 53 days) with no automatic cleanup. This PR fixes several root causes:
Database health (storage/db.ts, project/bootstrap.ts): Enable incremental auto-vacuum (one-time migration) so disk space is reclaimed on deletes. Add periodic WAL checkpoint (every 5 min) and incremental vacuum (hourly) via Scheduler.
Bash/shell output spooling (tool/bash.ts, session/prompt.ts): Both accumulated ALL stdout/stderr in an unbounded in-memory string. Now output beyond 50 KB streams to a spool file on disk. Only a 50 KB preview stays in memory. Full output is recoverable via the spool file path in metadata.
Compaction clears dead data (session/compaction.ts): Compacted tool parts kept their full output in SQLite even though toModelMessages() skips them. Now clears output/metadata/attachments on compaction.
Levenshtein optimization (tool/edit.ts): Replace O(n×m) full-matrix with O(min(n,m)) 2-row algorithm. For 10K char strings: ~400 MB → ~40 KB. Identical results.
Memory leak fixes (file/time.ts, lsp/client.ts, util/rpc.ts): Clean FileTime per-session state on archive/delete. Delete empty LSP diagnostics map entries. Add 60s timeout to RPC pending calls.
Session retention (session/index.ts, config/config.ts): Auto-delete archived sessions older than retention.days (default 90, 0 = disabled). Runs every 6 hours, batched at 100 per run.
How did you verify your code works?
- Edit tool tests: 27/27 pass
- Compaction tests: 24/24 pass
- Bash tool tests: 16/16 pass (includes new spooling test)
- TypeScript typecheck: clean
- Measured process memory on a live instance to identify the issues
Screenshots / recordings
N/A — backend changes only.
Checklist
- [x] I have tested my changes locally
- [x] I have not included unrelated changes in this PR
Linked Issues
#16777 High memory usage and database bloat in long-running OpenCode instances
View issueComments
PR comments
binarydoubling
Hey! We have a complementary PR at #16695 that tackles the in-memory side of these leaks — event listener accumulation in the TUI, unbounded Maps/Sets in the bus/RPC/LSP subsystems, timer/interval cleanup, and session data not being freed on switch. Between the two PRs, most of the memory growth paths should be covered.
BYK
Thanks for flagging! Checked #16695 — there are 3 overlapping files but the changes are complementary:
lsp/client.ts: Both PRs delete empty diagnostics entries. #16695 goes further with a 200-file LRU cap andclear()on shutdown — that's a superset of our change here, so no conflict. Happy to defer to theirs on this file.util/rpc.ts: Different code paths — ours adds a 60s timeout onpendingcalls (prevents leaked promises from dead workers), theirs cleans up emptylistenersSets (prevents tombstones). Both are needed and merge cleanly.session/prompt.ts: Different areas — ours is the output spooling rewrite (~line 1637), theirs rejects pending callbacks on cancel (~line 262). No overlap.
The rest is non-overlapping: our PR covers database health (auto-vacuum, WAL checkpoint, incremental vacuum), bash output spooling, compaction data clearing, Levenshtein optimization, FileTime cleanup, and session retention. Their PR covers TUI event listener leaks, bus tombstones, PTY buffer cleanup, model refresh interval, and share-next disposal.
Between the two PRs most memory growth paths should indeed be covered 👍
Changed Files
packages/app/src/components/dialog-connect-provider.tsx
+1−1packages/app/src/components/dialog-custom-provider.tsx
+1−1packages/opencode/src/cli/cmd/tui/component/dialog-provider.tsx
+1−1packages/opencode/src/config/config.ts
+10−0packages/opencode/src/lsp/client.ts
+6−1packages/opencode/src/session/compaction.ts
+3−0packages/opencode/src/session/prompt.ts
+37−17packages/opencode/src/storage/db.ts
+19−0packages/opencode/src/tool/bash.ts
+54−17packages/opencode/src/tool/edit.ts
+9−12packages/opencode/src/util/rpc.ts
+11−4packages/opencode/test/tool/bash.test.ts
+32−0