[PR #13676] fix(opencode): reduce memory allocations in read tool #14766

Open
opened 2026-02-16 18:19:31 -05:00 by yindo · 0 comments
Owner

Original Pull Request: https://github.com/anomalyco/opencode/pull/13676

State: open
Merged: No


Fixes #13669

Reduce transient memory allocations in ReadTool:

  • Bounded file.slice(0, estimate).text() instead of file.text() for text reads (issue item 1)
  • Binary detection reads only first 4KB via file.slice(0, 4096).arrayBuffer() (issue item 2)
  • Async readdir for missing-file suggestions

Item 3 from the issue (image/PDF base64 size guard) is not addressed here — capping inline images would be a feature regression since all sizes currently work. That can be revisited separately if needed.

Tests

$ cd packages/opencode && bun test test/tool/read.test.ts --timeout 30000

bun test v1.2.20 (6ad208bc)

 33 pass
 0 fail
 59 expect() calls
Ran 33 tests across 1 file. [2.06s]

Memory verification

100MB text file read, peak RSS measured by polling the child process VmRSS.
The opencode launcher spawns a native binary child, so pgrep -P finds the actual process to sample.

STOCK=$(npm root -g)/opencode-ai/node_modules/opencode-linux-x64/bin/opencode
PATCHED=packages/opencode/dist/opencode-linux-x64/bin/opencode

# Generate 100MB fixture
(yes "line lorem ipsum dolor sit amet" || true) 2>/dev/null \
  | head -c 104857600 > /tmp/large.txt

# Measure one run (repeat for each binary)
measure() {
  OPENCODE_BIN_PATH="$1" opencode run \
    "Use the read tool on /tmp/large.txt with offset 1 and limit 2000. Reply OK." \
    >/dev/null 2>/dev/null &
  local parent=$!
  local child="" peak=0

  # Wait for native binary child to spawn
  for i in $(seq 1 40); do
    child=$(pgrep -P $parent 2>/dev/null | head -1)
    [ -n "$child" ] && break
    sleep 0.25
  done

  # Poll VmRSS every 50ms
  while kill -0 $parent 2>/dev/null; do
    local rss=$(awk '/^VmRSS/{print $2}' /proc/${child}/status 2>/dev/null)
    [ "${rss:-0}" -gt "$peak" ] && peak=$rss
    sleep 0.05
  done
  wait $parent 2>/dev/null
  echo "$peak"
}

echo "stock:   $(measure $STOCK) KB"
echo "patched: $(measure $PATCHED) KB"
rm /tmp/large.txt

Results (3 runs each):

label      run  peak_rss_kb
stock        1       590580
stock        2       602804
stock        3       600364
patched      1       394936
patched      2       387172
patched      3       386440

stock   mean=584 MB  stddev=6.3 MB
patched mean=380 MB  stddev=4.6 MB
delta   -35% peak RSS

Small file control (100 lines) — no regression:

stock-small   peak_rss = 356 MB
patched-small peak_rss = 378 MB  (within noise)

Edge cases verified

Case Result
Empty file (0 bytes) Reads successfully, reports 1 blank line
Single 5MB line (no newlines) Returns truncated first line
Offset past EOF (offset=9999 on 2-line file) Error: Offset 9999 is out of range
Binary file (random bytes) Error: Cannot read binary file
Unicode/multibyte (CJK, emoji, accents) All characters preserved correctly
**Original Pull Request:** https://github.com/anomalyco/opencode/pull/13676 **State:** open **Merged:** No --- Fixes #13669 Reduce transient memory allocations in ReadTool: - Bounded `file.slice(0, estimate).text()` instead of `file.text()` for text reads (issue item 1) - Binary detection reads only first 4KB via `file.slice(0, 4096).arrayBuffer()` (issue item 2) - Async `readdir` for missing-file suggestions Item 3 from the issue (image/PDF base64 size guard) is not addressed here — capping inline images would be a feature regression since all sizes currently work. That can be revisited separately if needed. ### Tests ``` $ cd packages/opencode && bun test test/tool/read.test.ts --timeout 30000 bun test v1.2.20 (6ad208bc) 33 pass 0 fail 59 expect() calls Ran 33 tests across 1 file. [2.06s] ``` ### Memory verification 100MB text file read, peak RSS measured by polling the child process VmRSS. The opencode launcher spawns a native binary child, so `pgrep -P` finds the actual process to sample. ```bash STOCK=$(npm root -g)/opencode-ai/node_modules/opencode-linux-x64/bin/opencode PATCHED=packages/opencode/dist/opencode-linux-x64/bin/opencode # Generate 100MB fixture (yes "line lorem ipsum dolor sit amet" || true) 2>/dev/null \ | head -c 104857600 > /tmp/large.txt # Measure one run (repeat for each binary) measure() { OPENCODE_BIN_PATH="$1" opencode run \ "Use the read tool on /tmp/large.txt with offset 1 and limit 2000. Reply OK." \ >/dev/null 2>/dev/null & local parent=$! local child="" peak=0 # Wait for native binary child to spawn for i in $(seq 1 40); do child=$(pgrep -P $parent 2>/dev/null | head -1) [ -n "$child" ] && break sleep 0.25 done # Poll VmRSS every 50ms while kill -0 $parent 2>/dev/null; do local rss=$(awk '/^VmRSS/{print $2}' /proc/${child}/status 2>/dev/null) [ "${rss:-0}" -gt "$peak" ] && peak=$rss sleep 0.05 done wait $parent 2>/dev/null echo "$peak" } echo "stock: $(measure $STOCK) KB" echo "patched: $(measure $PATCHED) KB" rm /tmp/large.txt ``` Results (3 runs each): ``` label run peak_rss_kb stock 1 590580 stock 2 602804 stock 3 600364 patched 1 394936 patched 2 387172 patched 3 386440 stock mean=584 MB stddev=6.3 MB patched mean=380 MB stddev=4.6 MB delta -35% peak RSS ``` Small file control (100 lines) — no regression: ``` stock-small peak_rss = 356 MB patched-small peak_rss = 378 MB (within noise) ``` ### Edge cases verified | Case | Result | |------|--------| | Empty file (0 bytes) | Reads successfully, reports 1 blank line | | Single 5MB line (no newlines) | Returns truncated first line | | Offset past EOF (offset=9999 on 2-line file) | `Error: Offset 9999 is out of range` | | Binary file (random bytes) | `Error: Cannot read binary file` | | Unicode/multibyte (CJK, emoji, accents) | All characters preserved correctly |
yindo added the pull-request label 2026-02-16 18:19:31 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#14766