Repository navigation
Description
Bug: opencode main thread spins one CPU core at ~100% indefinitely (often right after a model stream step) — no logs, no I/O
Summary
On a large multi-module project, the opencode process repeatedly enters a CPU-bound loop: the main thread pins a single core at ~98–100% for many minutes, producing no further log output and no disk/DB I/O while it spins. The onset is usually mid-session, immediately after a model stream step (it was also seen once right after bootstrap). The hung process sometimes does not respond to SIGTERM (requiring SIGKILL), though in other occurrences it did terminate on SIGTERM. This has recurred multiple times in the same project and looks like an infinite loop in opencode itself rather than resource exhaustion on the host.
Version & Environment
- opencode: 1.17.8 (confirmed latest;
opencode upgradereports "already installed") - Install method: standalone curl installer (
~/.opencode/bin/opencode) - OS: Ubuntu 24.04.4 LTS
- Kernel: 6.8.0-124-generic
- Arch: x86_64
- CPU: 8 cores · RAM: 15 GiB
- Provider/model: zai-coding-plan / glm-5.2
- File watcher backend: inotify (per log)
Project characteristics (likely contributing factors)
- Large multi-module Gradle/Kotlin monorepo
- ~2.1 GB on disk, 73,672 files total, of which ~12,432 live in
build/directories - 11 ×
node_modulesdirectories build/,.gradle/,node_modules/are all listed in.gitignore~/.local/share/opencode/opencode.dbhas grown to ~3.6 GB (despite log showingcleanup prune=7.days)
Observed behavior / evidence
From ~/.local/share/opencode/log/opencode.log for a single run (run=cc853d12):
07:42:57 message="creating instance" directory=/path/to/project
07:42:58 message=bootstrapping
07:42:58 message="all LSPs are disabled"
07:42:58 message="all formatters are disabled"
07:42:58 message="booting location services"
07:42:58 message="watcher backend" backend=inotify
07:42:59 message="project copy refresh done"
07:43:59 message=cleanup prune=7.days <-- LAST log line for this run
After 07:43:59 the process emitted zero further log lines and performed no DB/disk writes (opencode.db mtime stayed at 07:42), yet it kept running at ~98% CPU for ~9 minutes until it was killed.
Process snapshot during the hang:
PID USER %CPU %MEM ELAPSED STAT CMD
21731 clausi 98.2 13.0 08:05 Rl+ opencode
- State
R(running), 24 threads, ~2 GB RSS - In this occurrence
kill -TERMhad no effect (process kept running at 99%) and onlykill -KILLterminated it; in later occurrences the process did exit on SIGTERM — so SIGTERM handling is inconsistent during the hang - Host load and Docker containers were otherwise idle — this is a pure single-core busy loop, not host overload
The bootstrap-time silence above was the first observed incident. The later, profiled incidents (see below) started mid-session, right after a model stream step rather than at bootstrap.
Steps to reproduce (best understanding)
- Open opencode in a large multi-module Gradle project (tens of thousands of files, sizeable
build/output present). - Use a session that runs Gradle test builds (e.g.
./gradlew :service:test --rerun-tasks), generating heavy file churn underbuild/. - Start/restart opencode in that directory.
- Observe one core pinned at ~100% with the log going silent right after the
cleanup prune=...bootstrap line.
Reproducibility note: It is intermittent. A fresh, clean bootstrap of opencode in the same project did not reproduce it (the process bootstrapped normally and dropped to ~2% CPU, state
Sl). The hang appears correlated with a specific session state — the affected sessions had previously been running repeated Gradle test loops, and/or were resuming a large existing session.
Captured native stack profile (root cause: main-thread tight loop)
I have now captured native stack profiles (gdb thread apply all bt, 3 samples ~1s apart) of two independent hung instances. Both point to the same thing:
-
The main thread (
"opencode", the JS thread) is the only thread in stateRand is spinning at a fixed instruction. In the cleanest capture, all 3 samples show the main thread frozen at the exact same PC:Thread 1 (LWP 74166 "opencode"): #0 0x0000000003b3faad in ?? () #1 0x0000000000000000 in ?? () -
The earlier, separate hang (different PID, cwd = the project) hit the same address
0x0000000003b3faadin its first sample. → A deterministic busy loop at a fixed point in the opencode binary. -
All
HeapHelper(GC) threads are parked infutex/pthread_cond_timedwait— not GC. -
No syscall/kernel frames on the spinning thread — pure userspace CPU loop, no I/O (consistent with the silent log and idle disk during the hang).
-
Onset: the hang begins immediately after a model stream step (
message=stream providerID=... modelID=glm-5.2 ... agent=build), after which the log goes silent while CPU stays pinned.
The address has no symbol because the distributed binary is stripped (Bun single-file executable), so I can't resolve the JS/native frame name. A maintainer with symbols for the 1.17.8 release binary should be able to map 0x0000000003b3faad directly. Full profiles (all threads, per-thread CPU/wchan) available on request.
What would help confirm
I'm happy to provide the full raw profiles, or to re-capture with a JS-level profiler (e.g. Bun inspector / --inspect) if you can point me at the preferred way to attach to a live hung instance. A symbolicated build or a mapping for 0x0000000003b3faad in 1.17.8 would immediately localize the loop.
Workaround in place
A systemd watchdog now monitors per-process CPU and SIGKILLs any opencode process sustaining >90% of a core for >60s. This mitigates the symptom but not the root cause. The watchdog also auto-captures a gdb stack profile (all threads, 3 samples) of the hung process before killing it, so I can attach a real native stack trace from the next live occurrence.
Questions
- Is there a recommended way to scope/exclude directories (
build/,.gradle/,node_modules/) from the file watcher? - Is the ~3.6 GB
opencode.dbexpected, or a sign of a pruning/vacuum issue that could also be contributing?
Activity
github-actions commentedon Jun 19, 2026
This issue might be a duplicate of existing issues. Please check:
If your hang is related to the diff/patch calculation after a stream step, #29762 is likely the most relevant. If it occurs during reconnection or tool use, #15600 may apply.
daveBifo commentedon Jun 19, 2026
As offered, here are the full raw stack profiles from the two captured runaways (sanitized; gdb
thread apply all btfor all threads + per-thread CPU/wchan from/proc):Key excerpt (incident #2, identical across all 3 samples ~1s apart):
All other threads (incl.
HeapHelperGC threads) are parked inpthread_cond_timedwait/futex, and there are no syscall frames on the spinning thread — i.e. a pure userspace busy loop on the main thread, not GC and not I/O.These were captured by attaching gdb to the live hung process before killing it. Happy to re-capture with a JS-level profiler if you can suggest the preferred way to attach (e.g. Bun inspector) to localize the loop above the native frame.
daveBifo commentedon Jun 19, 2026
Follow-up on triage / the auto-suggested duplicates.
This looks like the same bug as #29762 ("'step-end' diff calculation blocks event loop indefinitely with 100% cpu"). The captured native profiles here line up with that report point-for-point:
HeapHelper) parked — a blocked Bun event loop, exactly as "step-end" diff calculation blocks event loop indefinitely with 100% cpu #29762 describes.0x0000000003b3faadacross all gdb samples) — consistent with "step-end" diff calculation blocks event loop indefinitely with 100% cpu #29762's note that the blocked event loop makes it hard to profile.stream … glm-5.2 … agent=buildat step 54, then silence) — matches "after step-end".#29762 was filed against 1.15.11; this reproduces on 1.17.8, so it's still present. The gdb stack here (main thread frozen at a fixed PC, captured via attaching to the live hung process) may be useful corroboration since #29762 mentions profiling is hard due to the blocked loop.
For the record — #1617 (getpaseo/paseo) cross-links this issue but is a different root cause and does not apply here:
opencode servewith cwd=/, causingfff_searchto scan the entire filesystem. That signature is multi-threaded (Rayon, 300%+ CPU) and heavily I/O-bound (openat/stat/read).$HOME, never/), and the profile is the opposite: a single-thread, zero-I/O busy loop. So the cwd/fff_search-scan cause cannot be the explanation here.Happy to have this closed as a duplicate of #29762 if maintainers agree.
daveBifo commentedon Jun 19, 2026
Another captured occurrence — the spin starts right after a stream step inside a subagent, with no
write/editinvolvedI caught a fresh, clean occurrence with a 1 Hz
/procmemory sampler plus an auto-archived session log. This one is the single-core ~100% spin variant (not the multi-core/heavy-GC memory blow-up I mentioned separately), and it adds a useful data point: it is not triggered by a file diff.Process signature during the hang (1 s
/procsampling, native profile):R, 18 inS(plus parkedBun Pool/ watcher /HeapHelperthreads) — a single busy thread pinning one core.SIGTERMafter ~60 s (it did honorSIGTERMthis time).Onset (from the session log): the spin begins immediately after an LLM stream step, and this time that step was inside an
exploresubagent (agent=explore mode=subagent) which had just been doing a rapid sequence of read-onlygrep/glob/readtool calls. Nowrite/edit/apply_patchran before the freeze. So for this occurrence the loop is clearly not in file-diff computation — it points at step-end / streamed-response processing on the main thread.Sanitized tail of the log right before it went silent (paths, package, identifiers and domain terms anonymized):
So both occurrences I have now profiled look like the same step-end spin reached via two different paths: previously after a main-agent stream step, here after a subagent stream step that followed read-only tool calls. The common factor is "right after a stream step" — not file writes, not large diffs, not memory growth.
Environment unchanged (opencode 1.17.8, Bun,
glm-5.2viazai-coding-plan, Ubuntu 24.04, x86_64). I'm happy to share the full sanitized session log and the native per-thread profile on request.rekram1-node commentedon Jun 23, 2026
Automated: closing question issues.
GitHub Issues are for bug reports and feature requests only — questions / support are no longer tracked here.
If this is a bug or feature request, please open a new issue with the appropriate template.