Skip to content

Bash tool fails with 'Attempted to assign to readonly property' in v1.14.34 #25873

@stephanschielke

Description

@stephanschielke

Description

In v1.14.34+, the compiled + minified opencode binary fails tool execution with:

TypeError: Attempted to assign to readonly property

or:

TypeError: Attempting to define property on object that is not extensible

Also reported in #25835.

Root cause (confirmed)

Two contributing factors:

  1. Effect change: effect@4.0.0-beta.58 (Effect-TS/effect-smol#2098, "generate binary arrays from streams with less copying") changed Stream.mkUint8Array from an immutable Channel.runFold (new Uint8Array per iteration) to a mutable accumulator (acc.bytes += ...; acc.arrays.push(...); return acc). The bump from beta.57 to beta.59 was a routine chore (#25524) with no feature dependency.

  2. Bun compilation behavior: In bun build --compile --minify binaries, JSC makes the fold accumulator object non-extensible between iterations. The second chunk throws. This is consistent with known Bun --compile --minify issues (oven-sh/bun#13394, oven-sh/bun#12655).

Throw site: snapshot/index.ts:577 calls Stream.mkUint8Array(handle.stdout) to collect output from git cat-file --batch. processor.ts calls snapshot.track() at case "start-step" (after the first tool call completes, before the second starts). The error propagates as the next tool call's error.

Reproduction

Scenario Result
bun run (interpreted) ✅ Works
bun build --compile (no minify) ✅ Works
bun build --compile --minify + effect@4.0.0-beta.57 ✅ Works
bun build --compile --minify + effect@4.0.0-beta.58 or later ❌ Crashes on second tool call

Steps:

  1. Build opencode with bun run build --single using effect@4.0.0-beta.58 or later
  2. Start with opencode serve
  3. Trigger two consecutive tool calls in a session
  4. Second tool call fails with Attempted to assign to readonly property

Why it appeared "git-specific": The original failing session had 35 tool calls in Step 1 (no snapshot needed). Git commands started in Step 2. snapshot.track() fires at the step boundary. Testing with fewer tools per step confirmed a task tool (no git, no subprocess) also fails as the second call.

Evidence

  • Effect diff 4.0.0-beta.57..4.0.0-beta.59: exactly one change to Stream.mkUint8Array in Stream.ts (Effect-TS/effect-smol#2098)
  • opencode.db: all 4 original errors have fully parsed tool input (secureJsonParse succeeded), ruling out AI SDK JSON parsing
  • Error timestamps: 12ms elapsed (too fast for subprocess spawn), confirming the throw is in Effect/snapshot machinery
  • Reproduction: local-anthropic agent on broken binary, first call succeeded, second call (task tool, not git) failed

False leads eliminated

  • secureJsonParse finally block in @ai-sdk/provider-utils (DB proves parsing succeeded)
  • tool.ts wrap() mutation (runs at startup, not tool-call time)
  • "Only git commands fail" (disproved by task tool failing)
  • Bun minifier as sole cause (the mutable pattern is the trigger, Bun's --compile --minify object freezing is the environmental factor)

Fix

PR #25867:

  1. snapshot/index.ts: replace Stream.mkUint8Array with Stream.runForEach + local vars (actual crash site)
  2. git/index.ts: replace mutable Stream.runFold with Stream.runForEach + local vars
  3. Revert effect from 4.0.0-beta.59 to 4.0.0-beta.57 (last known-good)

Upstream

Once the upstream fix merges, the runForEach workarounds can be reverted and effect bumped again.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions