OpenCode / Memory lab
Shell jobs · 32 KiB output
Run completed shell jobs with 32768-byte outputs, using PID-tagged payloads to identify them in private heap snapshots.
Cycles / repetition
30 warm-up excluded
Heap change
Median case, end − baseline
Adjusted growth
Positive control drift removed
Repetitions
Fresh case + control processes
Post-GC heap
Completed cycles
40 cycles per batch · 3 full GC requests per checkpoint · All measured points shown · Y-axis zoomed
Growth detected. Post-GC heap growth exceeded the control-adjusted noise allowance and continued in the later batches of every repetition. This is a leak candidate, not a root-cause finding.
Idle GC & heap evidence
Raw snapshots are private and are not served by this website. Only filtered structural findings are published.
| Trial | Idle seconds | After work MiB | After idle GC MiB | Session messages |
|---|---|---|---|---|
| control-1 | 15 | 126.68 | 125.75 | 0 |
| case-1 | 15 | 139.53 | 136.18 | 0 |
| case-2 | 15 | 139.49 | 136.09 | 0 |
| control-2 | 15 | 126.64 | 125.60 | 0 |
Raw idle observations. These observations are separate from the slope fit; a lower heap after idle does not by itself establish that completed outputs were released.
Verified behavior & retention hypothesis
The process-global Job service stores completed jobs, their deferred results, metadata, and output in its Map. The shipped shell/subagent executors create jobs, but Job has no removal or bounded-exit cache, unlike Shell's 25-entry cache.
- Invoke the installed built-in shell executor, not a replacement.
- Each real command returns exit 0 and exactly 32768 bytes with the fixture prefix and a child PID. The command text stays constant across cycles.
- The fixed session's message history stays empty; the fixture retains only first/last shell IDs, never output history.
- control-1
- Functional preflight passed (2 case + 2 control cycles).
{"completed":2,"controlCompleted":232,"progressCalls":2,"outputBytes":65536,"bytesPerCommand":32768,"sessionMessages":0,"sessionID":"ses_f9bd8f3a8ffex4jAzFsbt0oq08","firstShellID":"sh_064270c99001CHoLafvmJ94nPg","lastShellID":"sh_064270cd50018QzGmiyjEGlF5i","outputMarker":"JOBMEM_LARGE:"} - case-1
- Functional preflight passed (2 case + 2 control cycles).
{"completed":232,"controlCompleted":2,"progressCalls":232,"outputBytes":7602176,"bytesPerCommand":32768,"sessionMessages":0,"sessionID":"ses_f9bd828d1ffeWvb2a0FojcBPzF","firstShellID":"sh_06427d760001GjIqYIPAYtmXvt","lastShellID":"sh_0642845a00019ExXR2GRPMpXTx","outputMarker":"JOBMEM_LARGE:"} - case-2
- Functional preflight passed (2 case + 2 control cycles).
{"completed":232,"controlCompleted":2,"progressCalls":232,"outputBytes":7602176,"bytesPerCommand":32768,"sessionMessages":0,"sessionID":"ses_f9bd76fd8ffe3d96H3jcsqV3or","firstShellID":"sh_064289073001JPlZWGl0fygbk9","lastShellID":"sh_06428fedb001pMDEPhokbMbHie","outputMarker":"JOBMEM_LARGE:"} - control-2
- Functional preflight passed (2 case + 2 control cycles).
{"completed":2,"controlCompleted":232,"progressCalls":2,"outputBytes":65536,"bytesPerCommand":32768,"sessionMessages":0,"sessionID":"ses_f9bd6b57affelGg1R344V8Yj2I","firstShellID":"sh_064294aba001DXyCJhAyNQq1mc","lastShellID":"sh_064294afd001HpXkgZw6cYakdo","outputMarker":"JOBMEM_LARGE:"}
Control cadence is calibrated from two real preflight cycles in each process and recorded as controlCadenceMs. Both arms get at least 20 seconds of warm-up/settling before the baseline. This approximately matches elapsed time, not every internal allocation or polling read.
Source inspected at 4772b6a3e8; this is the investigation reference, not a claim about the binary’s build commit.
packages/core/src/job.ts:187— Completion replaces the Map entry with a completed record and output; it does not remove it.packages/core/src/tool/plugin/shell.ts:242— Every shipped shell-tool execution starts a Job and waits for it.packages/core/src/shell.ts:29— The separate shell-process cache is capped at 25 exits.
Method & run details
One fixed session and one inspector transform remain. Commands finish before measurement; the fixture retains neither results nor a growing ID list. Session context remains empty.
Control. Run the same executable and output through a plain subprocess, without OpenCode's shell/job bookkeeping. Both arms are cadence-matched and warmed beyond the shell service's 25-exit cache.
Metric: target process.memoryUsage().heapUsed after Bun.gc(true). Warm-up excluded. Theil–Sen slope; later-half slope must remain positive. Subtract positive control drift only. A growing control, large baseline drift, or a noise allowance above max(1 MiB, 5% of baseline) that prevents detecting a trend makes attribution inconclusive. Net growth and slope × cycles must both exceed max(128 KiB, 6 × combined robust residual noise). Require agreement across at least two fresh-process repetitions. Heuristic, not a significance test.
- OpenCode
- 0.0.0-dev-18911 · dev channel · installed binary
- Drive
- 1.4.4 · installed package
- Runtime
- Bun 1.4.0 · linux / x64
- Isolation
- Private server + file-backed database. TUI closed before warm-up. Measurement plugin remains loaded; no object-level instrumentation.
- Checkpoints
- Case-specific cleanup checks pass before GC; 300 ms settling allowance. This is not a global background-work barrier.
- Snapshots
- Disabled. Use --snapshots for a diagnostic rerun.
- Run
- 2026-09-02T22-04-32-353Z-59f2dc · 2026-09-02T22:04:32.354Z
cd /root/projects/opencode-memory-lab && bun run run --case shell-jobs-large --warmup 30 --batches 5 --batch-size 40 --repetitions 2 --settle-ms 300 --warmup-ms 20000 --idle-ms 15000| Repetition | Case Δ MiB | Control Δ MiB | Adjusted B/cycle | Gate KiB |
|---|---|---|---|---|
| 1 | 11.90 | 1.00 | 39216.2 | 2207 |
| 2 | 11.91 | 1.04 | 39065.5 | 2185 |
Process RSS
Includes native allocations and allocator behavior. Not used to classify JS heap growth.
Measurements · 28 checkpoints
| Trial | Phase | Cycles | Heap MiB | RSS MiB | PID | GC ms |
|---|---|---|---|---|---|---|
| control-1 | warmup | 0 | 150.79 | 266.67 | 12106 | 94.8 |
| control-1 | baseline | 30 | 125.68 | 247.77 | 12106 | 84.6 |
| control-1 | measurement | 70 | 126.25 | 236.09 | 12106 | 76.4 |
| control-1 | measurement | 110 | 126.40 | 237.13 | 12106 | 70.4 |
| control-1 | measurement | 150 | 126.12 | 235.30 | 12106 | 74.3 |
| control-1 | measurement | 190 | 126.67 | 237.18 | 12106 | 67.6 |
| control-1 | measurement | 230 | 126.68 | 236.30 | 12106 | 110.8 |
| case-1 | warmup | 0 | 150.79 | 276.39 | 13824 | 99.5 |
| case-1 | baseline | 30 | 127.64 | 257.32 | 13824 | 79.7 |
| case-1 | measurement | 70 | 133.37 | 256.40 | 13824 | 69.2 |
| case-1 | measurement | 110 | 135.05 | 271.95 | 13824 | 74.3 |
| case-1 | measurement | 150 | 136.69 | 274.23 | 13824 | 89.5 |
| case-1 | measurement | 190 | 138.07 | 286.32 | 13824 | 117.1 |
| case-1 | measurement | 230 | 139.53 | 282.63 | 13824 | 72.4 |
| case-2 | warmup | 0 | 150.76 | 277.70 | 15341 | 92.9 |
| case-2 | baseline | 30 | 127.58 | 255.89 | 15341 | 84.6 |
| case-2 | measurement | 70 | 133.33 | 254.83 | 15341 | 80.3 |
| case-2 | measurement | 110 | 135.04 | 265.64 | 15341 | 93.0 |
| case-2 | measurement | 150 | 136.67 | 269.63 | 15341 | 133.8 |
| case-2 | measurement | 190 | 138.03 | 277.14 | 15341 | 76.9 |
| case-2 | measurement | 230 | 139.49 | 280.78 | 15341 | 73.4 |
| control-2 | warmup | 0 | 150.73 | 270.71 | 16860 | 90.3 |
| control-2 | baseline | 30 | 125.60 | 256.10 | 16860 | 83.8 |
| control-2 | measurement | 70 | 126.18 | 244.38 | 16860 | 74.9 |
| control-2 | measurement | 110 | 126.31 | 244.93 | 16860 | 79.0 |
| control-2 | measurement | 150 | 125.99 | 244.33 | 16860 | 69.2 |
| control-2 | measurement | 190 | 126.62 | 243.55 | 16860 | 80.2 |
| control-2 | measurement | 230 | 126.64 | 243.69 | 16860 | 65.8 |