Repository navigation
Description
Summary
OpenCode Desktop 2.0.18 (Windows): Preferences → Notifications → "Show system notification when permission is needed" (系统通知→权限) is enabled, and Windows itself allows OpenCode notifications (banner + sound, Do-Not-Disturb off) — yet when a permission request is pending, no system notification ever fires: no toast, no sound, no notification-center entry. Agent-completion notifications ("回复已就绪") from the same app are delivered fine, so the notification pipeline works; only the permission trigger is dead.
Environment
- opencode version: 2.0.18 (desktop app, Electron)
- OS: Windows 11, 10.0.26200 (win32 x64)
- Terminal: n/a (desktop app)
- Shell: PowerShell 7 (portable)
- Install/channel: latest release
- Active plugins: @prevalentware/opencode-goal-plugin, local handoff-on-compact (irrelevant — the notification is desktop-client behavior)
Reproduction
- In OpenCode Desktop: Preferences → Notifications → enable 系统通知→权限 ("Show system notification when permission is needed"); optionally enable 音效→权限.
- In Windows Settings → System → Notifications: global ON, 勿扰 OFF, OpenCode allowed with banner + sound.
- Configure an
askpermission rule for any tool (e.g.{ "action": "wincontrol_Clipboard", "resource": "*", "effect": "ask" }) and trigger it so a permission request goes pending. The in-app approval card ("需要权限", resource*, buttons 拒绝 / 始终允许 / 允许一次) appears and the tool call blocks. - Switch focus to a different application and wait.
Expected Behavior
A Windows toast using the app's own locale string {{sessionTitle}}({{projectName}})需要权限 (this template exists in the shipped zh locale bundle, so the feature is implemented at the UI level), plus the configured permission sound.
Actual Behavior
Nothing fires: no banner, no sound, and no entry in the notification center (verified via Win+N). The pending request exists only inside the unfocused OpenCode window, so the call blocks indefinitely with zero external signal.
Timeline of today's observations (2026-09-26, local UTC+8)
| ~Time | Event | System notification? |
|---|---|---|
| 21:07 / 21:10 | Two permission dialogs appeared (earlier session), user clicked Allow once | none recorded |
| 22:29 | Added ask rules for wincontrol tools, config hot-reloaded |
— |
| ~22:31 | Test A, window focused: wincontrol.Wait blocked 4.96 s total (~3 s human click latency), in-app dialog shown |
n/a (focused) |
| ~22:35 | Test B, deliberately Alt+Tabbed away: wincontrol.Wait blocked 43 s awaiting decision |
none — no banner, no sound (user report) |
| ~22:40 | Opened Windows notification center: four "回复已就绪" completion notifications present (21:03/21:57/22:13/22:41), zero "需要权限" | — |
| ~23:05 | Test C, user defocused: wincontrol.Clipboard(get) blocked 59 s; in-app "需要权限" card visible in screenshot |
none (user report) |
Frequency: 100% — every pending permission request today (at least 4) produced no system notification; two of them had the user explicitly defocused and watching for one.
Additional Context
- Completion notifications prove the Windows notification path works for this app; only the permission event never emits.
- The zh locale bundle contains both the setting description ("当需要权限时显示系统通知") and the notification title template ("{{sessionTitle}}({{projectName}})需要权限"), so the trigger appears simply not wired (or gated on a condition that never holds, e.g. a different event type than what the server emits for MCP-tool asks).
- Impact: users relying on
askto gate risky tools get no signal that a call is stuck while working in another window. Combined with the in-app prompt not stealing focus, the ask gate becomes a silent indefinite wait. - Related: permissions: ask rules for MCP tools are silently degraded to allow #51504 follow-up comment (retested there:
askitself works for MCP tools, including while unfocused — the request genuinely blocks; this issue covers the missing notification that is supposed to surface exactly that state).
Activity
github-actions commentedon Sep 26, 2026
This issue might be a duplicate of existing issues. Please check:
berkaykrc commentedon Oct 10, 2026
Confirming this same bug on the WebUI (not just the Electron desktop app), which suggests the missing trigger is in the shared notification module rather than anything Electron-specific. I also found two TUI-side problems that may all be the same underlying gap: notifications for sessions that are not in view, and notifications fired in auto mode for requests that were auto-approved anyway.
Finding 1 — WebUI: permission notifications are never emitted
Reproduction (WebUI)
opencode serveonhttp://127.0.0.1:49374display-mode: standaloneisfalse)Always approve/Approve one time/Reject, same as described hereWith the tab deliberately unfocused, three consecutive permission requests produced zero notifications. Completion notifications ("Response ready") arrived normally during the same session.
Root cause: no trigger is wired
The WebUI notification module has exactly two triggers, both gated on
session.execution.*:The events this module reacts to are only:
No permission-related event is handled in the notification path. Meanwhile
notifications.permissions()is read only by the settings UI binding, so the toggle persists a value nothing consumes:This is not the focus gate
The shared notify helper intentionally suppresses notifications while the tab is focused:
That gate is working as designed and is not the cause. Runtime state from the WebUI console while permission notifications were failing:
permission: grantedwithfocus: falsemeans the gate is open, which is why the completion notification was delivered under identical conditions.Finding 2 — TUI: notifications are not scoped to the session in view
The TUI does wire a permission trigger, but the listener set is attached to the global event stream with no filter for the session currently on screen:
hr()receives the session ID from the incoming event and never compares it against the session being displayed. The only distinction made isparentID(subagent sessions get sound only, no desktop notification).Reproduction
Observed directly: the session was running in the WebUI only, was not open in the kitty TUI, and the notification still arrived there (notification app name
kitty, message "Session done").Finding 3 — TUI: auto mode still fires permission notifications
Enabled auto mode (
session.permissions: "autoaccept"incli.json, TUI v2.0.29) and noticed that every permission request still raises a "Permission needs input" desktop notification + thepermissionsound in kitty — even though the request is auto-approved milliseconds later and the user never has to act on it. A notification for an approval nobody performs is pure noise.The attention path never consults the permission mode (built-in
opencode.notificationsTUI plugin):Meanwhile the auto-approver replies to the very same request right after it is created:
The pending-request UI is correctly suppressed in auto mode (
mode === "autoaccept"hides the request list), but the attention/notification path is not, so each auto-approved request still produces a desktop notification + sound. This is the same unscopedpr()helper as in Finding 2 — same listener set, same missing gating.Why the three findings are likely the same gap
permissionstoggle has no consumer — notifications never firepermission.askedtrigger ignores auto mode — notifications fire for requests that were auto-approvedAll three concern how permission/session events reach the attention module: one under-fires, the others over-fire.
Suggested fix
Wire a permission trigger in the WebUI notification module (subscribe to the permission-ask event and gate on
notifications.permissions(), mirroring the existingsession.execution.succeeded/.failedhandling), scope the TUI attention listeners to the session currently in view so unrelated sessions do not notify, and gate the TUIpermission.askedattention trigger (and its sound) onpermission.mode !== "autoaccept"— or drop notifications for requests that were already auto-replied.Additional notes
sw.js(Workbox) does not callclients.claim(), so a newly activated service worker does not take control of already-open tabs. This did not appear to affect this report and may be intentional.yiyaozhe commentedon Oct 19, 2026
Confirming on OpenCode Desktop 2.0.31 (Windows 11 26200): the in-app permission card appears and the session blocks, but no toast, sound, or notification-center entry is produced while the app is unfocused.
notifications.permissionsis enabled; completion notifications work.I checked the shipped desktop bundle and the permission setting has no consumer in the notification path: only
session.execution.succeededandsession.execution.failedare wired; there is nopermission.askedhandler. This matches the WebUI analysis above.Suggestion for the fix: in addition to wiring
permission.asked, consider an in-app always-on-top popup for pending permissions (or as a fallback when Windows toasts are suppressed by Focus Assist / per-app settings). A blocked permission request otherwise has no external signal. The popup can be notification-only with "open request" and "snooze/dismiss"; allow/deny from the popup would be a bonus.Thanks for #56044 — that looks like the right direction.
pskraemer11 commentedon Oct 19, 2026
Independent confirmation on OpenCode Desktop 2.0.32 (Windows 11, 10.0.26300,
@opencode-aidesktop), with the gap localised in the default branch.Confirmation
Same behaviour as reported: with
notifications.permissionsenabled and Windows notifications fully allowed for the app (banner + sound, no per-app mute, DND off), a pending permission request produces no toast, no sound and no notification-center entry while the app is unfocused. Completion notifications from the same app are delivered normally, so the notification pipeline itself works — the permission trigger is simply never reached.Root cause (default branch)
packages/app/src/shell/notifications/notification.tsx(310 lines) inspects only two event types:if (event.type === "session.execution.failed")if (event.type === "session.execution.succeeded") handleSessionIdle(event.data.sessionID, event.id)There is no
permission.askedbranch and noform.createdbranch anywhere in the file. The setting (notifications.permissions), its defaults and the i18n strings for the permission notification all exist and are rendered by the preferences UI, but nothing reads them in the notification path. This matches the bundle inspection on 2.0.31 earlier in this thread and the WebUI finding above: it is one shared gap in the notification module, not an Electron- or Windows-specific problem.The question flow has the same gap:
form.created(the question tool blocking for an answer) is likewise unwired, which is the desktop-side part of #49371.Workaround (local, no app changes)
Auto-discovered V2 server plugin, closes the gap for both events:
~/.config/opencode/plugin/<name>/index.js(Plugin.define+ctx.event.subscribe, reacting topermission.askedandform.created)ToastNotificationManagerThree non-obvious details, useful for anyone building the same thing or triaging this bug:
powershell.exemust not be spawned withdetached: true. It then exits silently with code 0 without executing the script. Droppingdetached(keepingwindowsHide) fixes it.Suggestion
Wiring
permission.askedandform.createdinto the shared notification module would fix the desktop app, the WebUI and every other host in one place. As a complement for the cases where OS toasts are suppressed (Focus Assist, per-app settings, muted channels), the in-app always-on-top popup suggested above would guarantee an external signal for a blocked request.opencodejr commentedon Oct 28, 2026
Thanks for the detailed report. I couldn't reproduce this here: it needs the desktop app showing real Windows notifications, which this sandbox can't run. A maintainer with the desktop app will need to confirm it.
The code supports what you describe, though. In 2.0.18 and on the current
devbranch (86ce19d), the app only sends system notifications when a response is ready or a session errors. The "Permissions" notification and sound settings are saved and shown in Preferences, but no code acts on them when a permission request is pending. That matches the behaviour you saw.Related:
askrule behaviour for MCP tools you mentionedHardcorePunk commentedon Oct 29, 2026
Confirming on 2.2.1 desktop app (Debian Trixie Linux, .deb install, /opt/OpenCode).
Completion sound (sounds.agent, staplebops-01) plays on task finish ✅
Error sound (sounds.errors, nope-03) plays on failure ✅
Permission sound (sounds.permissions, staplebops-02) never plays ❌
The "Permission required" system notification (notifications.permissions, default true) also never appears ❌
The Settings preview button for the Permissions sound does play, so the audio asset and the setting are fine. Grepping the shipped renderer bundle (out/renderer/assets/main-*.js in app.asar) shows sounds.permissions() / sounds.permissionsEnabled() have no call sites outside the settings UI, and the notification module's event listener only branches on session.execution.failed and session.execution.succeeded — no permission.asked case exists. Same root cause as the description. Verified still missing on dev as well.