Description
In v1.14.34+ (Bun 1.3.13), the compiled + minified opencode binary can intermittently fail tool execution with:
TypeError: Attempted to assign to readonly property
This was also reported in #25835.
What we know (verified)
| Scenario |
Result |
Run from source (bun run …) |
✅ Works |
Compiled binary with minify: false |
✅ Works |
Compiled binary with minify: true (production default) |
❌ Intermittent readonly-property TypeError |
Additional verified observations:
- The failure shows up in the full chat/tool-call runtime path (the one that goes through
ai.streamText({ tools })).
- The
/session/:sessionID/shell endpoint (direct shell execution) did not reproduce this error for us, even when exercised repeatedly.
What we don’t know yet
I previously claimed the thrown assignment was in Git.run()’s collect() helper (a Stream.runFold reducer with in-place accumulator mutation). That is not confirmed:
- I could not capture a stack trace pointing at that reducer.
- A compiled-binary stress test that calls
Git.run() directly did not reproduce the error.
- A minimal, network-free AI SDK tool-call harness (MockLanguageModel emitting a tool call) did not reproduce either.
So at this point, the exact throw site is still unknown.
The best current explanation is still: a Bun minifier/codegen bug that only manifests with the full opencode module graph + the streamText tool-calling pipeline (similar class to known Bun minifier issues).
Potentially related upstream context
Vercel AI SDK (@ai-sdk/provider-utils) has a known readonly-environment compatibility issue in its JSON parsing helper (secureJsonParse toggles Error.stackTraceLimit). Our dependency tree includes a guarded set (try { Error.stackTraceLimit = 0 } …) but the restore write is unconditional. This could be relevant since tool-call argument parsing uses safeParseJSON() → secureJsonParse(), but we do not have evidence yet that this is the actual throw site in opencode.
Proposed next step (to pin root cause)
Capture a stack trace from a failing compiled+minified binary with sourcemaps enabled, so we can identify the exact assignment that throws. Once we have the stack, we can decide whether the fix belongs in:
- opencode (avoid a minifier-sensitive pattern),
- the AI SDK (guard the restore write), or
- Bun (minifier/codegen bug report with a minimized repro).
Mitigation PR
PR #25867 replaces the Stream.runFold accumulator mutation in Git.run()’s collect() with Stream.runForEach + local variables. This eliminated the crash in the original reproduction we used for this issue, but (per above) the exact mechanism is not yet proven.
Description
In v1.14.34+ (Bun 1.3.13), the compiled + minified opencode binary can intermittently fail tool execution with:
This was also reported in #25835.
What we know (verified)
bun run …)minify: falseminify: true(production default)Additional verified observations:
ai.streamText({ tools }))./session/:sessionID/shellendpoint (direct shell execution) did not reproduce this error for us, even when exercised repeatedly.What we don’t know yet
I previously claimed the thrown assignment was in
Git.run()’scollect()helper (aStream.runFoldreducer with in-place accumulator mutation). That is not confirmed:Git.run()directly did not reproduce the error.So at this point, the exact throw site is still unknown.
The best current explanation is still: a Bun minifier/codegen bug that only manifests with the full opencode module graph + the
streamTexttool-calling pipeline (similar class to known Bun minifier issues).Potentially related upstream context
Vercel AI SDK (
@ai-sdk/provider-utils) has a known readonly-environment compatibility issue in its JSON parsing helper (secureJsonParsetogglesError.stackTraceLimit). Our dependency tree includes a guarded set (try { Error.stackTraceLimit = 0 } …) but the restore write is unconditional. This could be relevant since tool-call argument parsing usessafeParseJSON()→secureJsonParse(), but we do not have evidence yet that this is the actual throw site in opencode.Proposed next step (to pin root cause)
Capture a stack trace from a failing compiled+minified binary with sourcemaps enabled, so we can identify the exact assignment that throws. Once we have the stack, we can decide whether the fix belongs in:
Mitigation PR
PR #25867 replaces the
Stream.runFoldaccumulator mutation inGit.run()’scollect()withStream.runForEach+ local variables. This eliminated the crash in the original reproduction we used for this issue, but (per above) the exact mechanism is not yet proven.