Skip to content

Stop PWA notification pile-up and surface blocked sub-agents - #237

Merged
leoncheng57 merged 1 commit into
mainfrom
fix/pwa-notification-noise
Aug 26, 2026
Merged

leoncheng57 merged 1 commit into
mainfrom
fix/pwa-notification-noise

Conversation

@leoncheng57

Copy link
Copy Markdown
Owner

The report

Screenshots showed macOS Notification Center piled with "Needs approval to run bash" cards from the PWA — most already answered in the app — and a new requirement: a sub-agent blocked on a permission ask must be surfaced, while a sub-agent that merely finishes must not.

Three causes, three fixes

1. One replaceable OS notification slot per session

The push/notification tag was the record id, so every ask in a busy session piled its own card into the notification center, and Web Push cannot retract a shown notification — replacement via a shared tag is its only correction. notificationTag() keys the tag by session (record id only for sessionless records): a later ask overwrites the stale one, the parked escalation overwrites the ask it escalates, the eventual idle overwrites whatever is left. The server computes the tag once and stamps it on both the push payload and notification.recorded, so the service worker and an open tab can never disagree about identity. Collapsing is presentation only: the per-record dedupe still governs sound/speech.

2. Auto-approve survives a restart

The flag was memory-only (decision #11 "volatile"), so service:install at 14:01 today silently flipped this very directory back to ask mode and pushed one permission ask per tool call until it was re-toggled at 14:04 — the exact burst in the screenshots, and it would recur on every deploy. Flags now persist to .state/auto-approve.json (AUTO_APPROVE_STATE_FILE, mode 0600): restored on boot with pending-ask reconciliation (asks that arrived while the BFF was down get answered too), corrupt file fails closed to everything-off, an explicit toggle always wins over the startup load. Persisting an instruction the user already gave through the authenticated UI is not an escalation.

3. A blocked sub-agent is surfaced; a finished one is not

permission is now the one child event that takes the delivery path — a child stopped on an unanswered ask is stalled work nobody else can unblock, and suppressing it meant a delegated task sat frozen while the inbox swore nothing needed anyone. Its parked escalation follows the same policy (the lineage skip in scheduleParked is removed). A child ask in an auto-approved directory is still suppressed as auto-permissions — it was answered before anyone was blocked. Child idle/error/question stay suppressed: "subagent", recorded-only.

Coverage

  • Unit: child ask delivered / auto-approved child ask suppressed / other child kinds unchanged / child parked escalation / notificationTag identity / tag on push payload + recorded event / persistence round-trip incl. boot reconciliation, corrupt-file fail-closed, explicit-toggle-wins / client collapse under shared tag with per-record sound preserved.
  • E2E: smoke.api lineage test rewritten to the new policy (asks from any lineage delivered; child idle recorded as filterable noise); mock's /test/mobile/idle gained a directory param for lineage-correct fixtures; AUTO_APPROVE_STATE_FILE wired into the per-run state-file set (guard test enforces it).
  • The media spec's exact global speech-sequence assertion is relaxed to own only its phrases: one tab hears every directory by design, and parallel spec files now legitimately deliver child asks.

AGENTS.md decisions #10a, #11, #24 updated to match.

Verification

  • npm run typecheck clean; npm test 1184 passed.
  • Full Playwright suite: 365 passed, 1 skipped.

Note for after merge: existing stale cards in macOS Notification Center predate the tag change and need one manual clear; new notifications collapse from then on.

Three fixes for the over-notification report, one cause each:

1. Session-scoped OS notification tag. The tag was the record id, so a
   session that asked for bash seven times left seven stale "Needs
   approval" cards in the notification center. Web Push cannot retract a
   shown notification; replacement via a shared tag is its only
   correction. Each session now holds one replaceable slot: a later ask
   overwrites the stale one, the parked escalation overwrites the ask it
   escalates, and the eventual idle overwrites whatever was left. The
   server computes the tag once and sends it on both the push payload
   and notification.recorded, so the service worker and an open tab
   cannot disagree. Per-record dedupe still governs sound and speech.

2. Auto-approve flags persist across restarts. The flag was memory-only,
   so every deploy silently flipped an auto-approved directory back to
   ask mode and pushed one permission ask per tool call at every
   configured phone until the user noticed. Flags now live in
   .state/auto-approve.json (AUTO_APPROVE_STATE_FILE, 0600), restored on
   boot with pending-ask reconciliation; corrupt state fails closed and
   an explicit toggle wins over the startup load.

3. Sub-agent permission asks are delivered. A child stopped on an
   unanswered ask is stalled work nobody else can unblock; suppressing
   it meant a delegated task sat frozen while the inbox swore nothing
   needed anyone. Permission (and its parked escalation) is now the one
   child event that takes the delivery path; an auto-approved directory
   still answers child asks silently, and a child that merely finishes
   stays recorded-only.
@github-actions
github-actions Bot temporarily deployed to pr-preview-237 August 26, 2026 18:29 Destroyed
@leoncheng57
leoncheng57 merged commit a22e434 into main Aug 26, 2026
8 checks passed
github-actions Bot added a commit that referenced this pull request Aug 26, 2026
@leoncheng57
leoncheng57 deleted the fix/pwa-notification-noise branch August 26, 2026 18:33
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.

1 participant