Skip to content

fix: suppress notification spam on app re-open#623

Merged
Gagancreates merged 7 commits into
devfrom
fix/notification-spam
Jun 23, 2026
Merged

fix: suppress notification spam on app re-open#623
Gagancreates merged 7 commits into
devfrom
fix/notification-spam

Conversation

@prakhar1605

@prakhar1605 prakhar1605 commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator

fix: background agent notifications bypass toggle and fire when focused

What changed

The notify-user tool was calling notificationService.notify() directly for background-agent notifications, bypassing the user's Background agents toggle in Settings and ignoring the onlyWhenBackground rule.

Why it happened

All notification toggles are enforced by one function — notifyIfEnabled(). The notify-user tool detects background agents by reading getCurrentUseCase() via AsyncLocalStorage. But ALS does not propagate across the runner's async generator — withUseCase() only wraps the trigger (createMessage), not the actual agent execution loop. So by the time notify-user fires, getCurrentUseCase() returns undefined, the background_task_agent branch is skipped, and it falls through to a direct service.notify() — toggle ignored, onlyWhenBackground ignored.

The fix

When getCurrentUseCase() returns falsy but ctx.runId is present, load the persisted useCase from the run record via fetchRun(ctx.runId). The run's useCase is written at creation time and isn't subject to ALS propagation. A lazy import() avoids the known module-init cycle (builtin-tools → runs/runs → agents/runtime → builtin-tools).

Background-agent notifications now correctly:

  • respect the Background agents toggle
  • fire only when the app is in the background (onlyWhenBackground: true)
  • deep-link to the bg-tasks page

Files affected

  • apps/x/packages/core/src/application/lib/builtin-tools.tsnotify-user ALS fallback via fetchRun(ctx.runId)
  • apps/x/packages/core/src/agents/runtime.ts — removed leftover diagnostic log

Also included: status-tracker.ts had the same class of bug — coding-session events (needs-you, idle) called service.notify() directly. Routed through notifyIfEnabled() (agent_permission / chat_completion). Note: this path is not yet manually tested end-to-end.

How to test (background agents — Arjun's spec)

  1. cd ~/Desktop/rowboat/apps/x && npm run deps && npm run dev
  2. Settings → Notifications → Background agents OFF
  3. Background agents → Test Notify → Run now → no notification
  4. Background agents ONCmd+H → Run now → notification fires + deep-links to bg-tasks
  5. App focused → send "hello" → no notification
  6. Cmd+H → send "hello" → "Response ready" notification
  7. Settings → Chat responses OFF → Cmd+H → send "hello" → no notification

prakhar1605 and others added 7 commits June 14, 2026 16:57
- Add background_task notification category (default ON) so users can
  toggle off background-task pings via Settings → Notifications
- Route notify-user builtin through notifyIfEnabled gate so the
  category toggle takes effect
- Add 60s startup grace period: background-task notifications fired
  within 60s of launch are suppressed, killing the reopen flood where
  all queued agents complete at once
- Suppress new_email notifications for emails older than 5 min so
  Gmail's startup backlog replay doesn't surface day-old mail

Fixes both issues reported by Ramnique.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Background task runs were triggering "Response ready / Your agent
finished responding" on every completion. Skip the automatic
chat_completion notification when finalState.runUseCase ===
'background_task_agent' — background tasks notify explicitly via
notify-user when they have something worth surfacing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- runtime.ts: also skip chat_completion ping for knowledge_sync
  useCase (agent_notes_agent was opening notes view on click)
- sync_gmail.ts: new_email notification now links to specific
  email thread (rowboat://open?type=email&threadId=...)
- App.tsx: add email case to parseDeepLink, parse threadId param,
  wire threadId through applyViewState, update viewStatesEqual

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per team clarification, two background types are handled differently:
- knowledge_sync (auto knowledge-graph generation): never notifies;
  skips the generic chat_completion ping entirely.
- background_task_agent (user-configured agents): notifies via its
  own notify-user path, gated behind the toggleable "Background
  agents" category, deep-linking to the background-tasks page.

- runtime.ts: skip the generic chat_completion completion ping for
  both knowledge_sync and background_task_agent (the latter notifies
  via notify-user, so the generic ping would duplicate it).
- builtin-tools.ts: notify-user branches on getCurrentUseCase() —
  background agents route through notifyIfEnabled('background_task')
  with a bg-tasks deeplink default; chat agents notify directly.
- App.tsx: add the bg-tasks deeplink target (ViewState, parseDeepLink,
  applyViewState, currentViewState).
- settings-dialog.tsx: rename the category label to "Background agents".
- runner.ts: wrap the background-task run in withUseCase so tools see
  the correct use-case context.
Code-mode status-tracker was calling notificationService.notify() directly,
bypassing the user's notification-category toggles. Routed both calls
through notifyIfEnabled() with correct categories:
- needs-you state → agent_permission
- idle after 30s  → chat_completion

Removed now-redundant container/INotificationService plumbing.
…n notify-user

AsyncLocalStorage does not propagate across the background-task agent's
async generator — getCurrentUseCase() returns undefined inside notify-user,
causing the background_task_agent branch to be skipped and the Background
agents toggle to be ignored.

Fix: load persisted useCase from run record via fetchRun(ctx.runId) when
ALS is falsy. Lazy dynamic import() avoids the known module-init cycle.
Background-agent notifications now correctly respect the toggle and only
fire when the app is in the background, with deep link to the bg-tasks page.
# Conflicts:
#	apps/x/apps/main/src/main.ts
#	apps/x/apps/renderer/src/App.tsx
#	apps/x/packages/core/src/background-tasks/runner.ts
@prakhar1605 prakhar1605 requested a review from Gagancreates June 23, 2026 14:02
@Gagancreates Gagancreates merged commit 1bfda94 into dev Jun 23, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants