You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
bug: TUI session hangs with InstanceRef not provided rejection after LSP initialization (v1.15.2) #27880
In TUI mode, after a tool (e.g., apply_patch) modifies a file, the LSP server initializes and immediately triggers an unhandled InstanceRef not provided rejection. The session appears to hang (stuck at "Preparing patch..." or similar) after this error.
The root cause is in packages/opencode/src/lsp/lsp.ts:294:
Bus.publish(Event.Updated,{})
This static Bus.publish call is made from inside an Effect.promise callback (line 215), where there is no Effect fiber context. Bus.publish internally calls InstanceState.get → InstanceState.context → yield* InstanceRef, which fails because no fiber context is available.
This is the same class of bug as #27829, #27833, #27838, #27857 — Effect.runPromise or equivalent inside Effect.promise losing fiber context. Those were fixed in v1.15.2 (commits 88363f1, 6d2219e) but the lsp.ts call site was missed.
While a targeted fix (providing InstanceRef via .pipe(Effect.provideService(InstanceRef, ctx)) at the call site) would address this specific instance, PR #25694 proposes an infrastructure-level fix at InstanceState.get() that ensures InstanceRef is always propagated into ScopedCache lookups regardless of how the caller provides it. That approach would prevent this entire class of bugs from recurring across any call site that goes through InstanceState.
Log output
INFO service=permission permission=edit pattern=Sisyphus.md evaluated
INFO service=format file=Sisyphus.md formatting
INFO service=bus type=file.edited publishing
INFO service=lsp file=Sisyphus.md touching file
INFO service=lsp serverID=remark spawned lsp server
INFO service=lsp.client serverID=remark sending initialize
INFO service=lsp.client serverID=remark initialized
ERROR service=default e=InstanceRef not provided rejection
Steps to reproduce
Start opencode TUI (opencode)
Have a session where the model uses a tool that modifies a file (e.g., apply_patch)
This can be a resumed/continued session — the bug is not session-state dependent
In my case, it was a .md file being edited
After the tool applies changes, the post-edit hook triggers format → LSP touch → LSP spawn
Observe InstanceRef not provided rejection in logs (~/.local/share/opencode/log/)
The session may hang or become unresponsive after this point
Adding another data point: I can confirm this still happens on OpenCode 1.15.3.
I have three local crash logs from the same flow. Two were on 1.15.1, but the important one was already on 1.15.3: both the parent TUI session and the child subagent session logged version=1.15.3 before the failure.
This was in TUI/subagent usage, with elixir-ls being initialized after the subagent flow touched project files. So v1.15.3 looks like it fixed the earlier async command / agent generation call sites, but not this LSP-after-initialize path.
Experiencing the same bug with biome and marksman LSPs on opencode-ai v1.15.3 (Windows). Disabling LSPs as workaround. Watching for the fix in PR #25694.
I have the same issue and tried debug a little bit. This following paragraph is the analysis from Opus 4.7. Could be worthwhile putting into a context for fixing this crashing issue.
Opus 4.7 analysis
Adding another v1.15.1 data point with a different escalation path: same root-cause signature, but in our case it terminates the whole TUI process via Bun SIGTRAP rather than just logging the rejection.
Environment
opencode 1.15.1 (also reproduced on 1.15.3)
bundled Bun v1.3.13 (bf2e2cec)
macOS 26.3.1 (Apple M2)
terminal: Ghostty / xterm-ghostty
Reproduced on two separate M2 machines, both running curl-installed opencode v1.15.0 does not exhibit this — confirmed by downgrade (opencode upgrade 1.15.0) on both machines.
Trigger
TUI session with an active subagent (Sisyphus - Ultraworker parent → @explore subagent). Pressing Ctrl+X to switch into the running subagent session reliably triggers the chain. On the second occurrence, the parent TUI crashes to terminal with a Bun panic.
Log signature (sanitized, from ~/.local/share/opencode/log/)
INFO service=session id=ses_<parent> version=1.15.1 agent=Sisyphus - Ultraworker created
INFO service=session id=ses_<child> version=1.15.1 parentID=ses_<parent> title=Map auth module surface (@explore subagent) created
...
INFO service=lsp.client serverID=elixir-ls path=/.../lib/.../router.ex workspace/didChangeWatchedFiles
INFO service=lsp.client serverID=elixir-ls path=/.../lib/.../scope.ex workspace/didChangeWatchedFiles
ERROR service=default e=InstanceRef not provided rejection
INFO service=session.prompt session.id=ses_<child> cancel
INFO service=session.prompt session.id=ses_<parent> cancel
INFO service=lsp.client serverID=elixir-ls path=/.../lib/.../user_auth.ex workspace/didChangeWatchedFiles
ERROR service=session.processor session.id=ses_<parent> error=Aborted process
ERROR service=session.processor session.id=ses_<parent> error=Aborted process
So this matches the exact path you identified: LSP client initialised → workspace/didChangeWatchedFiles → InstanceRef not provided from inside Bus.publish in lsp.ts. Same call site, elixir-ls instead of remark.
Escalation: process-level SIGTRAP
On the next user input after this rejection, the whole TUI crashes with a Bun panic instead of a clean rejection:
Bun v1.3.13 (bf2e2cec) macOS Silicon
macOS v26.3.1
panic: Segmentation fault at address 0x13D30
SIGTRAP
lsp.ts:294 rejection aborts the session processor mid-flight while @parcel/watcher still holds an NAPI threadsafe function reference back into the Effect runtime.
The next callback from the native watcher thread fires napi_call_threadsafe_function into a callback whose surrounding Effect scope has been torn down, dereferencing freed memory inside Bun's ConcurrentTask.createFrom.
This is consistent with the NAPI deferred-finalizer bug class Bun fixed in 1.3.14 (release notes). opencode bundles 1.3.13, so the underlying NAPI race is latent — v1.15.0 happens to avoid tripping it; the v1.15.1 InstanceRef rejection path appears to trip it on subagent transition.
The parcel-watcher callback site in packages/opencode/src/file/watcher.ts also changed in v1.15.1 (InstanceState.bind → EffectBridge.make().bind), which is the other call site I'd suspect of dangling-callback-after-scope-end. Worth checking whether the bridge finalizer can run while parcel's native thread still has the callback pointer in flight.
Why this is a separate failure mode worth tracking
The "rejection + hang" surface you described in the OP is bad UX but recoverable. The variant I'm seeing crashes the entire bun process, taking the parent TUI and any running subagents down with it — auth/session state survives, but mid-flight work is lost and there is no .ips dump (process exits via SIGTRAP without handing off to Apple CrashReporter).
Happy to file this as a separate issue specifically targeting the SIGTRAP escalation if that's preferable to keeping it bundled here. Either way, the infrastructure-level fix in InstanceState.get() (PR #25694) plus auditing remaining Effect.promise callsites that call InstanceState-backed APIs (notably packages/opencode/src/file/watcher.ts) would close both surfaces.
step_start → tool_use(read) → (process exits with code 1, no further events) ❌
No step_finish, no text, no error event — the process terminates silently.
Debug log capture
INFO service=lsp.server serverID=deno root=/path/to/project/apps/web spawned lsp server
INFO service=lsp.client serverID=deno starting client
INFO service=lsp.client serverID=deno sending initialize
DEBUG service=lsp.client serverID=deno text=Starting Deno language server...
version: 2.7.14 (release, aarch64-apple-darwin)
INFO service=lsp.client serverID=deno initialized
ERROR service=default e=InstanceRef not provided rejection ← crash point
The Deno LSP initializes successfully, then the rejection fires immediately after initialized.
Notes
The Hermes Agent opencode plugin tool was initially suspected, but direct CLI usage proves the bug is in opencode itself
The --format json event stream is cut off before any step_finish event, so downstream tooling (like the Hermes plugin) correctly reports status: error, exit_code: 1 but gets no actionable error message — the error only surfaces in opencode's own debug logs
Workaround "lsp": false in ~/.config/opencode/opencode.json confirmed effective, but obviously degrades the coding experience
Reproduction 2, actual opencode run --format json read-tool path:
timeout --kill-after=2s 35s bun --conditions=browser src/index.ts --print-logs --log-level DEBUG run --format json --dir ../.. "Use the read tool to read the file lsp-instance-ref.repro. Do not use shell commands."
I also reproduced the same path via a Sisyphus primary agent delegating to a Sisyphus-Junior subagent. In that case the subagent performed the read, then hit the same LSP initialization failure.
My read is that PR #25694 is still useful hardening for InstanceState.get() / ScopedCache.get(...), but this issue’s direct LSP path needs an additional fix in packages/opencode/src/lsp/lsp.ts.
The failure still matches the Bus.publish(Event.Updated, {}) call inside the Effect.promise(async () => ...) block. At that point the code has crossed into plain async JS, and the module-level Bus.publish runtime re-entry no longer has the original InstanceRef.
So PR #25694 can preserve an InstanceRef once InstanceState.get() has one, but it cannot synthesize one for this separate runtime re-entry.
A targeted fix would be to capture the instance context before the async boundary and explicitly provide InstanceRef when publishing Event.Updated, e.g.:
import{InstanceRef}from"@/effect/instance-ref"import{makeRuntime}from"@/effect/run-service"constbusRuntime=makeRuntime(Bus.Service,Bus.layer)// inside getClients(), after `const ctx = yield* InstanceState.context`voidbusRuntime.runPromise((bus)=>bus.publish(Event.Updated,{}).pipe(Effect.provideService(InstanceRef,ctx),),)
This mirrors the existing safe pattern in packages/opencode/src/lsp/client.ts, where LSP callbacks publish bus events through a runtime while explicitly providing the captured instance context.
Activity
masterkain commentedon May 16, 2026
Adding another data point: I can confirm this still happens on OpenCode
1.15.3.I have three local crash logs from the same flow. Two were on
1.15.1, but the important one was already on1.15.3: both the parent TUI session and the child subagent session loggedversion=1.15.3before the failure.Sanitized sequence from the
1.15.3log:This was in TUI/subagent usage, with
elixir-lsbeing initialized after the subagent flow touched project files. Sov1.15.3looks like it fixed the earlier async command / agent generation call sites, but not this LSP-after-initialize path.ririnto commentedon May 16, 2026
Until Next Release ...
~/.config/opencode/opencode.json{ "lsp": false }masterkain commentedon May 16, 2026
but I love the smell of lsp in the morning
CloudSmithBrandon commentedon May 16, 2026
Experiencing the same bug with biome and marksman LSPs on opencode-ai v1.15.3 (Windows). Disabling LSPs as workaround. Watching for the fix in PR #25694.
zentetsukenz commentedon May 16, 2026
I have the same issue and tried debug a little bit. This following paragraph is the analysis from Opus 4.7. Could be worthwhile putting into a context for fixing this crashing issue.
Opus 4.7 analysis
Adding another v1.15.1 data point with a different escalation path: same root-cause signature, but in our case it terminates the whole TUI process via Bun SIGTRAP rather than just logging the rejection.
Environment
1.15.1(also reproduced on1.15.3)Bun v1.3.13 (bf2e2cec)v1.15.0does not exhibit this — confirmed by downgrade (opencode upgrade 1.15.0) on both machines.Trigger
TUI session with an active subagent (
Sisyphus - Ultraworkerparent →@exploresubagent). Pressing Ctrl+X to switch into the running subagent session reliably triggers the chain. On the second occurrence, the parent TUI crashes to terminal with a Bun panic.Log signature (sanitized, from
~/.local/share/opencode/log/)So this matches the exact path you identified:
LSP clientinitialised →workspace/didChangeWatchedFiles→InstanceRef not providedfrom insideBus.publishinlsp.ts. Same call site,elixir-lsinstead ofremark.Escalation: process-level SIGTRAP
On the next user input after this rejection, the whole TUI crashes with a Bun panic instead of a clean rejection:
Stack (from
bun.report):Bun report: https://bun.report/1.3.13/Ma1bf2e2cemxHuhoqC_2+iydmmqhZ_____A2Agz+E
My read on the escalation:
lsp.ts:294rejection aborts the session processor mid-flight while@parcel/watcherstill holds an NAPI threadsafe function reference back into the Effect runtime.napi_call_threadsafe_functioninto a callback whose surrounding Effect scope has been torn down, dereferencing freed memory inside Bun'sConcurrentTask.createFrom.1.3.14(release notes). opencode bundles1.3.13, so the underlying NAPI race is latent —v1.15.0happens to avoid tripping it; the v1.15.1InstanceRefrejection path appears to trip it on subagent transition.The
parcel-watchercallback site inpackages/opencode/src/file/watcher.tsalso changed in v1.15.1 (InstanceState.bind→EffectBridge.make().bind), which is the other call site I'd suspect of dangling-callback-after-scope-end. Worth checking whether the bridge finalizer can run while parcel's native thread still has the callback pointer in flight.Why this is a separate failure mode worth tracking
The "rejection + hang" surface you described in the OP is bad UX but recoverable. The variant I'm seeing crashes the entire
bunprocess, taking the parent TUI and any running subagents down with it — auth/session state survives, but mid-flight work is lost and there is no.ipsdump (process exits via SIGTRAP without handing off to Apple CrashReporter).Happy to file this as a separate issue specifically targeting the SIGTRAP escalation if that's preferable to keeping it bundled here. Either way, the infrastructure-level fix in
InstanceState.get()(PR #25694) plus auditing remainingEffect.promisecallsites that callInstanceState-backed APIs (notablypackages/opencode/src/file/watcher.ts) would close both surfaces.srivenkateswaran6002 commentedon May 17, 2026
is this issue still open for contributions ? I can try and come up with a fix.
intpfx commentedon May 17, 2026
Adding another reproduction case with additional diagnostic detail — this time with Deno LSP + TSX files in headless mode (not TUI).
Environment
deno(for.tsxfiles)opencode run --format json(headless, non-TUI)deepseek/deepseek-v4-proReproduction (100% reliable)
Smaller files in the same project succeed:
deno.json(28 lines JSON) → ✅ exit 0, full text responseBrandToggle.tsx(147 lines TSX) → ❌ exit 1, no textJSON event stream comparison
Success case (deno.json, 28 lines):
Failure case (BrandToggle.tsx, 147 lines):
No
step_finish, notext, noerrorevent — the process terminates silently.Debug log capture
The Deno LSP initializes successfully, then the rejection fires immediately after
initialized.Notes
opencodeplugin tool was initially suspected, but direct CLI usage proves the bug is in opencode itself--format jsonevent stream is cut off before anystep_finishevent, so downstream tooling (like the Hermes plugin) correctly reportsstatus: error, exit_code: 1but gets no actionable error message — the error only surfaces in opencode's own debug logs"lsp": falsein~/.config/opencode/opencode.jsonconfirmed effective, but obviously degrades the coding experienceToken usage (for the successful small-file case, for reference)
The LLM call that succeeds shows the agent correctly processes the tool output:
{"type":"step_finish","tokens":{"total":57399,"input":2039,"output":200,"reasoning":120,"cache":{"write":0,"read":55040}}}So the failure is definitely at the framework level (LSP → DI), not an LLM API issue.
ririnto commentedon May 17, 2026
I tested PR #25694 locally against a minimal LSP reproduction, and it does not appear to fix this specific LSP-after-initialize failure path.
Setup:
devversion=localat runtime;packages/opencode/package.jsonis1.15.3oh-my-openagentplugin enabled/loadedInstanceState.get()capturesctxand providesInstanceRefaroundScopedCache.get)opencode.jsonthat enables a fake stdio LSP for a.reprofileReproduction 1, direct LSP debug command:
Result:
Reproduction 2, actual
opencode run --format jsonread-tool path:timeout --kill-after=2s 35s bun --conditions=browser src/index.ts --print-logs --log-level DEBUG run --format json --dir ../.. "Use the read tool to read the file lsp-instance-ref.repro. Do not use shell commands."Result:
I also reproduced the same path via a Sisyphus primary agent delegating to a Sisyphus-Junior subagent. In that case the subagent performed the read, then hit the same LSP initialization failure.
My read is that PR #25694 is still useful hardening for
InstanceState.get()/ScopedCache.get(...), but this issue’s direct LSP path needs an additional fix inpackages/opencode/src/lsp/lsp.ts.The failure still matches the
Bus.publish(Event.Updated, {})call inside theEffect.promise(async () => ...)block. At that point the code has crossed into plain async JS, and the module-levelBus.publishruntime re-entry no longer has the originalInstanceRef.So PR #25694 can preserve an
InstanceRefonceInstanceState.get()has one, but it cannot synthesize one for this separate runtime re-entry.A targeted fix would be to capture the instance context before the async boundary and explicitly provide
InstanceRefwhen publishingEvent.Updated, e.g.:This mirrors the existing safe pattern in
packages/opencode/src/lsp/client.ts, where LSP callbacks publish bus events through a runtime while explicitly providing the captured instance context.