anomalyco / anomalyco/opencode
Desktop: spawning the app executable as a child process (e.g. plugin hooks via process.execPath) activates the main window on every tool call
@Hona is already working on this.
Since Aug 6, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
Description
The OpenCode desktop app (Electron) activates its window (comes to front / steals focus) whenever a plugin spawns the app's own executable as a child process — for example, when a plugin runs hook scripts via process.execPath (which, inside the desktop app's embedded opencode server, is the OpenCode Helper binary).
Impact
Any plugin that runs Node scripts by spawning process.execPath (a common pattern: the plugin's own process.execPath is guaranteed to be a Node runtime) will cause the main window to pop to the front on every tool call, even while the user is actively working in another app. In our case (the GSD workflow plugin, which runs 7 hooks around each tool call), the window pops constantly during agent work.
Environment
- OpenCode Desktop 1.18.14 (Electron), macOS 15.5 (Sequoia)
- Plugin: GSD core plugin (
plugins/gsd-core.js) spawning hooks viaspawnSync(process.execPath, [hookPath])
Reproduction
- Install any plugin that runs a hook/script via
spawnSync(process.execPath, ...)on tool events (PreToolUse/PostToolUse). - Keep the OpenCode window unfocused (user in another app).
- Run any tool call.
- Observed: the OpenCode window activates itself and jumps to the front.
Evidence (unified log)
At every tool boundary, a new OpenCode Helper process registers, then the main process calls SLPSSetFrontProcessWithInfo and the window orders front (~15 ms later). The spawned helper's argv is the hook script:
11:36:47.570 OpenCode[47170] kLSNotifyChildApplicationReady { "ApplicationType"="UIElement", "BundleIdentifierLowerCase"="ai.opencode.desktop.helper", ... }
11:36:47.585 OpenCode[47170] GetFrontProcess() → SLPSSetFrontProcessWithInfo(... window=0 options=0x1)
11:36:47.585 WindowServer: set front 47170, focus 47170
11:36:47.585 OpenCode[47170] order window front conditionally: f48
Captured helper process (via process snapshots at 20 ms intervals during the activation):
OpenCode Helper /Users/<user>/.config/opencode/hooks/gsd-workflow-guard.js
OpenCode Helper /Users/<user>/.config/opencode/hooks/gsd-context-monitor.js
The shell tool itself does not spawn any process; the activation is strictly correlated (3/3 reproductions) with the plugin's process.execPath child spawns.
Expected behavior
Spawning the app's own binary in headless script mode must not activate the main window. A background child process of the app should never steal focus from the user's active application.
Suggested fixes (any one would help)
- Main process: do not call
SLPSSetFrontProcessWithInfo/win.show()/win.focus()in response to child-helper LaunchServices registration. Only activate on genuine user intent (second-instance deep link with a URL, notification click, dock click). - Server runtime: run the embedded opencode server with a real Node.js runtime (standalone sidecar /
nodeutility withprocess.execPath= node) instead of the Electron app binary, soprocess.execPathinside plugins is a plain Node executable. - Plugin API: document/provide a way for plugins to obtain a standalone Node binary path (e.g.,
context.nodePathor similar), so plugins do not have to fall back to spawning the app binary.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.