-
Notifications
You must be signed in to change notification settings - Fork 11.4k
Description
Description
High CPU Usage & Blocked Event Loop (3 Infinite Loops Identified)
Version: Found in v1.2.15 (and earlier builds)
Symptom: OpenCode Desktop and opencode-cli processes consume 100% of a single CPU core, leading to highly degraded performance, stuttering AI text generation, and stalled network requests (due to the main Node.js event loop being blocked).
Issue 1: Missing End-Of-File (EOF) handling in Rust Sidecar reader
Location: packages/desktop/src-tauri/src/cli.rs -> read_line()
Introduced in: 4025b655a (desktop: replicate tauri-plugin-shell logic)
Description:
The async loop responsible for reading stdout/stderr from the spawned CLI sidecar does not explicitly handle the Ok(None) case returned by lines.next_line().await when the output stream is exhausted or closed. This causes an infinite non-yielding loop in Tauri.
Fix:
match line {
Ok(Some(s)) => {
let _ = tx.clone().send(wrapper(s)).await;
}
Ok(None) => break, // ADDED: Break on stream EOF
Err(e) => {
let tx_ = tx.clone();
let _ = tx_.send(CommandEvent::Error(e.to_string())).await;
break;
}
}Issue 2: Zero-delay reconnection loop in Global Event Subscription
Location: packages/opencode/src/acp/agent.ts -> runEventSubscription()
Introduced in: bef1f6628 (fix(acp): use single global event subscription and route by sessionID)
Description:
The global event subscription is wrapped in a while (true) loop to ensure it reconnects if the event stream terminates. However, there is no backoff or sleep mechanism. If the stream closes instantly (e.g., due to an immediate disconnect or recurring error), the while loop spins unthrottled, locking the event loop.
Fix: Add a delay before restarting the loop iteration.
for await (const event of events.stream) {
if (this.eventAbort.signal.aborted) return
const payload = (event as any)?.payload
if (!payload) continue
await this.handleEvent(payload as Event).catch((error) => {
log.error("failed to handle event", { error, type: payload.type })
})
}
// ADDED: Prevent CPU thrashing on instant disconnects
await new Promise((resolve) => setTimeout(resolve, 1000))
}Issue 3: Empty string infinite loop in MultiOccurrenceReplacer
Location: packages/opencode/src/tool/edit.ts -> MultiOccurrenceReplacer()
Introduced in: f39a2b1f1 (integrate gemini-cli strategies for edit tool)
Description:
The generator function yields indexes of a find string. If the LLM hallucinates or triggers a call where the search string is empty (find === ""), content.indexOf(find, startIndex) will constantly return 0 (or startIndex). Because startIndex = index + find.length evaluates to startIndex + 0, the index never advances, causing a permanently blocked synchronous while (true) loop. This directly stalls the Node.js event loop, paralyzing all concurrent operations and AI responses.
Fix:
export const MultiOccurrenceReplacer: Replacer = function* (content, find) {
if (find.length === 0) return // ADDED: Guard against empty search strings
let startIndex = 0
while (true) {
const index = content.indexOf(find, startIndex)
if (index === -1) break
yield find
startIndex = index + find.length
}
}Plugins
N/A
OpenCode version
v1.2.15
Steps to reproduce
Launch Opencode and observe launch times, model response and cpu usage.
Screenshot and/or share link
N/A
Operating System
Ubuntu 24.04
Terminal
GNOME