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
20 cycles per batch · 3 full GC requests per checkpoint · All measured points shown · Y-axis zoomed
Inconclusive. The run is incomplete, too short, or does not show a consistent settled trend. Do not interpret this as a pass.
Snapshot finding: completed Job records persist. After idle GC, all 132 completed jobs still have closed scopes and fixture-tagged output nodes. Each command emitted 32 KiB. The separate shell cache holds 25 entries and no longer contains the oldest job. This structural result is separate from the memory-trend verdict above.
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.57 | 125.48 | 0 |
| case-1 | 15 | 135.61 | 131.84 | 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.
Records are matched by the Core Job.Active reference fields and fixture session, not by class name alone. Fixture marker and child-PID prefixes identify their output nodes. The two control jobs are the real preflight commands.
| Trial / snapshot | Executed | Job records | Closed scopes | Tagged outputs | Shell cache |
|---|---|---|---|---|---|
| case-1 / baseline | 32 | 32 | 32 | 32 | 25 |
| case-1 / final | 132 | 132 | 132 | 132 | 25 |
| case-1 / idle | 132 | 132 | 132 | 132 | 25 |
| control-1 / baseline | 2 | 2 | 2 | 2 | 2 |
| control-1 / final | 2 | 2 | 2 | 2 | 2 |
| control-1 / idle | 2 | 2 | 2 | 2 | 2 |
Snapshot limitation. Bun exports these job objects without incoming edges, so no complete GC-root retaining path is available. Missing paths do not establish collectibility. A two-process calibration using the same embedded Bun reproduces this for a live Map-only value, while a direct object property has an incoming edge.
The actual record → info → output reference edges are preserved in the filtered data. Bun truncates string labels to 1,024 characters, so the snapshot identifies output prefixes rather than verifying all payload bytes. Exact output size was checked during execution. Output-node shallow bytes are not total retained size.
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":132,"progressCalls":2,"outputBytes":65536,"bytesPerCommand":32768,"sessionMessages":0,"sessionID":"ses_f9bdd9fb7ffeLoIky5kCuYjEER","firstShellID":"sh_064226076001gopBdIh0I0Vnd2","lastShellID":"sh_0642260bc003ei8m2kfelSXjRi","outputMarker":"JOBMEM_LARGE:"} - case-1
- Functional preflight passed (2 case + 2 control cycles).
{"completed":132,"controlCompleted":2,"progressCalls":132,"outputBytes":4325376,"bytesPerCommand":32768,"sessionMessages":0,"sessionID":"ses_f9bdcd07affeQYoLv2UZAJ0Nr6","firstShellID":"sh_064232fc5001KpAAbslOM7SXvF","lastShellID":"sh_064239bb50015tSUYQSv54kWKN","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
- Baseline and final, plus post-idle snapshots enabled. Raw heaps are private under the run's .cache directory, not served by this website.
- Run
- 2026-09-02T21-59-26-177Z-95f1bd · 2026-09-02T21:59:26.177Z
cd /root/projects/opencode-memory-lab && bun run run --case shell-jobs-large --warmup 30 --batches 5 --batch-size 20 --repetitions 1 --settle-ms 300 --warmup-ms 20000 --idle-ms 15000 --snapshots- A single repetition is a smoke test, not a repeatability check
| Repetition | Case Δ MiB | Control Δ MiB | Adjusted B/cycle | Gate KiB |
|---|---|---|---|---|
| 1 | 7.66 | 0.61 | 48668.6 | 3440 |
Process RSS
Includes native allocations and allocator behavior. Not used to classify JS heap growth.
Measurements · 14 checkpoints
| Trial | Phase | Cycles | Heap MiB | RSS MiB | PID | GC ms |
|---|---|---|---|---|---|---|
| control-1 | warmup | 0 | 150.76 | 273.25 | 4092 | 96.6 |
| control-1 | baseline | 30 | 125.96 | 251.50 | 4092 | 174.4 |
| control-1 | measurement | 50 | 126.16 | 251.91 | 4092 | 172.8 |
| control-1 | measurement | 70 | 126.23 | 262.63 | 4092 | 173.2 |
| control-1 | measurement | 90 | 126.30 | 263.91 | 4092 | 218.2 |
| control-1 | measurement | 110 | 125.99 | 262.29 | 4092 | 204.7 |
| control-1 | measurement | 130 | 126.57 | 252.70 | 4092 | 175.5 |
| case-1 | warmup | 0 | 150.74 | 270.91 | 5035 | 104.3 |
| case-1 | baseline | 30 | 127.95 | 272.41 | 5035 | 226.8 |
| case-1 | measurement | 50 | 131.93 | 268.93 | 5035 | 195.3 |
| case-1 | measurement | 70 | 133.36 | 270.62 | 5035 | 224.9 |
| case-1 | measurement | 90 | 134.27 | 273.22 | 5035 | 233.4 |
| case-1 | measurement | 110 | 135.03 | 276.85 | 5035 | 212.1 |
| case-1 | measurement | 130 | 135.61 | 295.33 | 5035 | 209.1 |