Skip to content

Notifications

A failed attempt to turn notifications off keeps the last known setting and shows Could not check notification delivery. Try turning them off again when connected. Muting a computer cancels its pending alerts, just like muting a session; unmuting allows future alerts only.

Turn off -> confirmed removal -> off
         -> failed removal    -> keep setting + show problem

Temporary problems reading saved names do not prevent an alert appearing; it uses roamd as its title instead. A failed alert does not stop the other alerts in the same delivery. Retrying an old completion does not clear a newer request for input.

Dismissals are saved on this device before they are sent. If you are offline, Saved on this device. Will sync when connected. means the change is queued. It stays tied to your account and retries when the app reconnects, reopens, or receives another push. Until the server receives it, other devices can still show the alert. Clearing website storage removes pending local changes.

Dismiss -> save here -> reconnect -> server confirms -> other devices sync

Signing out, remotely revoking a browser, or letting its login expire stops new alerts to that browser. Locking the vault keeps notifications enabled. Browser delivery-address renewals are retried without asking for permission again. Slow delivery to one device does not hold up all the others.

Notifications distinguish Sent to push service, Reached device, Requesting display, Display requested, and Delivery failed. A display request is not proof that you saw a banner. The encrypted push also contains a generic fallback, so a failed background fetch need not hide the alert. Computer and session names are still added only on your device. Android delivery has been confirmed on a real device; the revised Apple path still needs a physical background-and-lock-screen test.

Session status is detected on the remote computer and passed through the hub encrypted. The remote computer alone decides whether a session is working, waiting for you or idle; your browser shows what it last said and never times that out. Checking status… appears only while the computer is offline, or before this page has heard from it. Opening, closing or resizing a terminal on it doesn't change a session's light. A completion estimate requires an observed new work cycle, not an initial working label. Where an AI app's hooks are installed, the app itself says when it is working or waiting for you; otherwise the computer judges from the screen, which is not proof an agent has finished.

Your computer watches each session -> decides: working / waiting / idle
  -> sealed report -> hub -> your browser shows it, as it was said
Computer offline, or not heard from since the page loaded -> faint grey ring

Opening Notifications checks this browser's current setting again. While that check is pending, roamd does not offer to turn notifications on. An existing subscription with permission granted stays Notifications on, even if the server cannot be reached. Could not check notification delivery. and Check again report that separate problem. Status unavailable means the app could not determine the setting; it does not mean you turned it off. No permission question is repeated for an already-authorised browser.

Open Notifications -> check this browser's setting
                              |
                     check delivery separately

The notification panel shows recent activity as cards with the computer and session name, time, read state and delivery status. Unread cards have a blue outline and an Unread label. Mark all read sits above the list. The header keeps Close and this device's notification status visible while you scroll. Expand Notification settings for delivery tests and the optional agent setup steps. Turning notifications on is also offered there.

Notifications + device status + Close
-------------------------------------
Notification settings (expand for tests)
Recent activity             Mark all read
Alert cards                 (scroll)

The notifications line in a session's ⋯ menu (top right of the tab bar) turns notifications for that session on or off across your devices. Alerts start on. The crossed-out bell means that session is muted; turning it on still requires notifications enabled on each receiving device. A computer's Mute alerts setting takes priority. The choice is saved in your encrypted vault and follows the session when you close and reopen its tab. A newly named session has its own setting. Muting cancels pending alerts and prevents new ones being sent. Turning alerts back on only allows future events. Alerts already on your devices are taken away shortly after.

⋯ > notifications -> save choice -> all devices use that choice

Open Notifications in the main menu. It contains your recent alerts, Mark all read, a test button, and the setup steps for each computer.

Allow browser notifications
          |
Update roamd on each computer
          |
Session stops / input is needed
          |
Check both events in Notifications

Activity alerts work automatically after updating roamd on each computer. When a busy session becomes idle, the alert says Session may need you. A recognised question or approval prompt says Input required. These use the same activity indicator shown in roamd; quiet output can also mean an agent is thinking or waiting for a tool, and a question can be missed or mistaken for ordinary text. No hook setup is required for these estimates.

On an iPhone or iPad, open the copy of roamd installed with Add to Home Screen, then allow notifications. Choose Send test notification to check that device's delivery separately from the AI agent setup. The sent test first checks that the app's background notification handler responds. If it says The notification worker did not respond. Reload the app and try again., use the app's update/reload control and retry. If it says The push registration is incomplete. Turn notifications off and on, then try again., the browser has not supplied the registration's encryption material; reconnect notifications before testing again. The sent test and normal alerts use encrypted generic fallback notifications. No computer/session names or conversation content are included. If no alert appears, choose Test on this device. This asks the device to display a test directly, without sending a push. Keep the app open and look for the banner. If that works but the sent test does not, the failure is in remote delivery. If neither appears, the problem is in device display or permission. Check Lock Screen, banners and Focus in system settings. A successful display request is not proof that a banner was visible.

Optional: more precise detection. Hooks let the agent report a completed reply or input request directly, using the same two alert messages. Once hooks are observed in the running agent, roamd avoids duplicate activity estimates. For Codex running inside a roamd tmux session:

  1. Update roamd on that computer to the release containing agent notifications.
  2. In a terminal on that computer, run roamd notifications.
  3. Restart Codex. Open /hooks, review and trust the roamd hooks. Keep your existing hooks. If /hooks is unavailable, update Codex first.
  4. Finish a short turn, then exercise an approval request. Check that Session may need you and Input required both appear in Notifications.

The setup writes hooks but does not approve them for you. An agent event appearing proves that event reached roamd; it does not prove every hook has been approved. If the list says Waiting for the first agent event, check the computer's setup. If it says Delivery failed, use the test button and check the device's notification settings.

Delivery failed means that alert could not be sent. It does not by itself mean notification permission is off. Check the status of a new alert after a delivery fix; old failed entries are part of the shared history.

For other agents, run the matching setup command on the computer where the agent runs. Restart that agent afterwards. Review and approve any hook or plugin prompt it shows; roamd never approves agent permissions for you.

Agent Setup command Input alerts Finished alerts
Codex roamd notifications Approvals and question tools Confirmed completed turn
Claude Code roamd notifications claude Approvals and AskUserQuestion Stop hook
Gemini CLI roamd notifications gemini Tool permissions and ask_user AfterAgent hook
Copilot CLI roamd notifications copilot Permission and information notifications agentStop hook
OpenCode roamd notifications opencode Permission and question events Main session idle event
Cursor roamd notifications cursor Terminal question detection only Completed stop hook
Goose roamd notifications goose Question tools and terminal question detection Stop with an answer
aider roamd notifications aider Terminal question detection only Custom notification command

The aider setup prints a launch command; use that command, or add its notification options to your existing aider settings. Other installers preserve your settings and hooks and keep a backup. Claude Code settings must allow the new hooks; check /hooks after restarting. Gemini hooks must be enabled and the folder trusted; check /hooks. Copilot hooks must be enabled; review its hook permissions. Cursor user hooks and the OpenCode/Goose plugins must be allowed by your agent's policy. Cursor must run in the tmux terminal; an unrelated editor window cannot be mapped safely to a terminal session.

A quiet-screen alert is an estimate, not proof of a completed turn. Agents other than Codex report their documented completion hook; another hook can continue a turn, so those completion events are less conclusive than Codex's saved completion record. Always test both event kinds you need after setup.

The notification title uses the computer and session names last seen on this device. Those names are kept locally while notifications are enabled; they do not go to the push provider or expose session names to roamd's server. A session this device has not seen yet uses the computer name or roamd instead. Turning notifications off or locking/signing out clears the saved names.

Tapping or dismissing a system alert, opening its session, or marking it read consumes it across your account. It is then expired for delivery to your other devices, even when they open later. A later state change can create a new alert. The inbox keeps read history; consumed alerts are not shown again as new.

New alerts go to all browsers where you enabled notifications. Watching the relevant session suppresses alerts across your devices and marks those events read. Opening that session marks its previous alerts read; opening the menu alone does not. A completed tool or later turn can mark an input request handled. Read and handled state is shared through your account, so opening another device does not replay the history as new notifications. Tapping an alert opens its session when available; if the computer or session is unavailable, the inbox still opens.

Notification permission normally stays granted in that browser or installed app. roamd re-registers an existing subscription without asking again when opened. Permission can remain allowed even when this app has lost its delivery registration. Send test notification reconnects an already-authorised app before sending, without asking for permission again. An explicit off stays off until you enable notifications or ask to send a test. Clearing website data, reinstalling the app or revoking permission can require setup again. Permission in an ordinary Safari tab is separate from the installed Apple app.

A sent alert is not proof that you saw it. The list distinguishes recording, push-service acceptance, display on a device, read, and handled state. A device that is asleep or using Focus may delay delivery. An alert already in transit may still arrive after you handled it; it is then taken away again at once.

An alert appears as soon as it arrives, even on a slow connection; the computer and session names are filled in a moment later. If the details can't be fetched, the alert keeps its plain words, such as Input required.

Brave has push messages switched off until you change one setting. If turning notifications on fails in Brave, open Brave's settings, go to Privacy and security, turn on Use Google services for push messaging, then try again. roamd says this when it happens. Chrome, Edge, Firefox and Safari need nothing extra.

Safari can drop an app's delivery registration without saying so. When roamd opens and notifications were on here, with permission still allowed, it quietly registers again. It never does this after you turned notifications off here, or for a different account. Dealt with anywhere clears it everywhere. When you tap, dismiss or mark an alert read, open its session, or answer it on the computer, roamd takes that alert away from your other devices too, even ones where roamd is closed. Apple devices must show something for every push, so there the alert's words change to Dealt with on another device for a moment, without a sound, and then it is gone. If an alert reaches a device after it was dealt with, it is not shown. A device that is off or offline is cleared when it next gets the message.

Dealt with on one device -> server -> "take it away" push -> other devices
                                                               |
                                 close it (Apple: quiet words in its place, then close)

Disable checks both server removal and browser unsubscription before reporting success. Failures retain the last known enabled state and expose a delivery check error. Computer mute and pending-event consumption share a transaction; events raised while muted start consumed. Completion insertion and input resolution are atomic, so a replayed completion cannot handle newer input.

New completion -> insert once + resolve prior input
Duplicate      -> no change to newer input

Optional worker client enumeration and cached-label reads are bounded and failures do not abort display. Individual display failures do not stop later events; successfully displayed events still receive receipts. Cached names fall back to the application name. These checks exercise worker handlers with controlled browser boundaries, not actual operating-system notification trays.

Dismissals and delivery-stage receipts use transactional browser storage, separately from shell assets and display labels. Only opaque account/event identifiers, delivery addresses, stages and timestamps are stored. Reads are retried for their original account, and are acknowledged explicitly. Local consumed markers remain through the event-retention period, preventing a late push or stale inbox response from replaying an acknowledged event. Retries run on worker wake, foreground/connectivity return, and background sync where supported. Browser storage eviction or clearing can still remove unsynchronised changes.

Local transaction -> scoped retry -> matching server acknowledgement
                         |
                 another account: retain, do not consume

Push addresses are bound to a browser login. Revocation removes that binding; expiry and organisation suspension also exclude it from delivery. Vault locking does not revoke the login. A replacement push address is registered before the old one is removed, preserving only that browser's scheduled events. Renewal is retryable across worker restarts and cannot override explicit disable. An already-sent push cannot be recalled from a sleeping device.

Notification requests and browser lookups have deadlines. DNS resolution and transport are bounded; recipient delivery has bounded concurrency, so a stalled recipient does not block all remaining devices. Scheduled sends recheck revocation and consumption before contacting the provider.

Registered Web Push encryption material lets both tests and normal event pushes carry a standards-based declarative fallback: generic title/body and opaque event/account scope, with no terminal text or machine/session names. It contains no vault or SSH decryption material. WebKit can present this fallback if the worker fails. In every browser the worker shows the fallback first, before any network request, then replaces it (same tag) with the locally named version. Provider acceptance, worker receipt, display request and display result are distinct stages. Receipts retry durably; none proves human attention. Physical Apple verification remains.

push --> local checks (read here? other account?) --> show fallback now
                                                          |
                     fetch pending (bounded) <------------+
                       | listed     -> replace with named alert
                       | not listed -> "activity updated", or close if roamd is focused
                       | failed     -> fallback stays
empty push (no payload) --> fetch --> named alert, else one plain alert
                                      (none while a focused roamd window shows it)

Chrome replaces a silent push with its own text, and Safari and Firefox can stop delivering to a site whose pushes show nothing; so every push not covered by a focused, visible roamd window ends with a notification on screen.

The push key is fetched when the explanation opens, so the browser subscription follows the permission answer without a network wait (Safari requires subscription close to the tap). Brave rejects subscription with a generic push service error while its Google push setting is off; roamd detects Brave and names the setting.

On open and foreground, a browser with granted permission, a previous local registration, no explicit disable, and no current subscription subscribes again. The registration carries the previous account scope; a conflict (another account) unsubscribes the browser again. Apple's browsers do not fire pushsubscriptionchange, so this is their only renewal path.

open -> permission granted? -> subscription gone? -> enabled here before?
          -> subscribe -> register (same scope) -> 409: unsubscribe

The remote watcher alone classifies each session; the browser never re-judges or ages a report. A session is unknown only on first sight (roamd starting). On pane identity, geometry, attachment, window or process changes the previous state is held while delayed redraws settle, so viewers joining, leaving or resizing never change it. Initial working snapshots cannot arm an estimated completion. Where the app's hooks are installed, their events are recorded per app process and take precedence over the screen: a prompt or finished tool means working, a permission or input request means waiting, and stop, interrupt or session end means idle. A hook's waiting holds until the screen has moved on for more than one look; a hook's working stops being believed once neither the screen nor a newer hook has changed for a while. Busy reports are renewed promptly. The browser shows unknown only while the machine is offline or before a report has been confirmed since the page loaded; a failed status fetch keeps the last report.

look -> first sight?           -> unknown
     -> layout/viewer changed? -> keep previous state while it settles
     -> hook for this process? -> waiting | working (while fresh) | screen
     -> screen                 -> prompt: waiting; moved lately: working; else idle
report (sealed, signed) -> hub -> browser: online and confirmed? shown : hollow ring

The enabled state comes from browser permission and the local push subscription. Subscription synchronisation failure is a separate state and cannot overwrite an existing enabled setting. Unknown and in-progress reads do not expose an enable action. The inbox refreshes on open and when it returns to the foreground; concurrent refreshes share one request, and superseded results are ignored. Explicit disable still removes the subscription. An enabled browser state is not proof of provider acceptance or an operating-system banner.

Permission + local subscription -> enabled state
Server registration check      -> delivery-check state

The inbox separates a fixed header from a scrollable body. It retains the existing account-wide event, read and delivery data, with explicit state text alongside the visual unread treatment. Settings and hook approval instructions are collapsed disclosures; opening them does not change permissions or consume alerts. Mark all read is disabled when no unread, unhandled events remain. Keyboard Escape, Close or a backdrop click dismisses the dialog.

Open inbox -> event cards -> open session / mark read
                  |
             settings disclosure -> tests / optional setup

Each session preference is a revision-bound encrypted vault item. The same transaction saves an unencrypted delivery rule containing only the owner's device identifier, opaque session token and muted flag. This lets the hub enforce the choice even when browsers are closed. The hub learns that a session is muted, but receives no session names or terminal contents. Revision conflicts cannot partially save the preference or delivery rule. Other unlocked clients refresh through the existing vault change stream. Muting consumes pending notices; events recorded while muted are already read and cannot be replayed on unmute. Existing computer-wide mutes still apply. Notifications already displayed are cleared by the clear push described below.

Bell -> sealed vault item + opaque delivery rule (one save)
                                  |
                      muted: no new push queue

Delivery status belongs to an event/subscription attempt and is distinct from notification permission. A transport failure can leave earlier events marked failed; a later display receipt updates the corresponding delivery record.

The sent delivery test checks the active worker through a bounded message exchange, then uses the same encrypted fallback format as normal event delivery. Its routing and display stages are recorded separately.

Test on this device invokes the active worker registration's display API directly, with existing permission, without creating a subscription or contacting the push provider. A rejected display request is reported separately. An accepted display request cannot prove that the operating system presented a banner.

Device test -> browser display API -> system alert
Sent test   -> push provider -> worker -> system alert

The account inbox and push subscription are separate. Each machine emits opaque event/session identifiers and an event kind. The browser resolves a session identifier using the machine's signed, encrypted status, not a session name supplied by the hub.

Screen activity or optional hook
          |
     local outbox -> shared inbox -> push service
                                  |               |
                           read / handled     service worker
                                  |               |
                                  +----- browser -+

Screen activity transitions produce automatic alerts without agent setup. The daemon remembers transitions in memory, so a fresh start or reappearing session does not replay an already-idle screen or existing question. Working-to-idle produces an advisory activity alert; entering a recognised waiting state produces an input alert. Leaving that state handles that input alert. A hook observed in the same agent process suppresses duplicate estimates for the event types its adapter supports. Process identities remain machine-local.

Install the optional Codex integration with roamd notifications, then restart Codex and approve the installed definitions in /hooks. Existing hook definitions are preserved and a backup is kept. The installer does not modify Codex's trust decisions. Hooks must be allowed by the user's Codex policy.

Hooks bind to their inherited tmux pane. Approval requests and explicit question tools produce input events. Matching tool completion resolves a request; a later prompt, interruption, session end, or completed turn resolves outstanding requests. A Stop hook is only a candidate: a matching completed-turn entry in that conversation's local record is required before notifying. Codex's transcript format is not a stable public interface; incompatible records can prevent completion detection and are never treated as successful detection.

Each browser reports short-lived presence only while focused on a connected, visible terminal. Presence and read state are account-wide. Read and handled timestamps are distinct from push acceptance and display receipts. A system notification click or dismissal also records shared read state. A queued event consumed on one device is excluded when another device fetches pending alerts. Transient delivery failures are retried with a bound, and expired subscriptions are removed. Old events are not scheduled merely because a new browser subscribes or opens.

The test button resolves and re-registers this app's current subscription using the same browser driver as the notification switch. With permission already granted it can recreate a missing subscription. A failed server registration leaves the subscription intact and is never reported as enabled. Refreshing alone does not recreate subscriptions after the person explicitly turned them off. The test button targets this browser's subscription. Service-worker receipts mean the browser accepted a display request, not that a human read it. Events already in transit can race with read updates; the worker then takes the alert away again in the same way as a clear push.

Clearing: when an event becomes read or handled (inbox, system notification click or dismissal, presence on its session, a handled or resumed hook event, a completion resolving input, or a mute), the hub sends an encrypted clear push, {"clear":{"id","scope"}}, to every subscription it sent that alert to (accepted, received, display requested or displayed) that has payload keys. The browser that did the reading sends its own push endpoint with the read or presence call and is skipped. Each subscription is cleared once per event; failures are retried a bounded number of times by the delivery retry loop, and only recent events are cleared. The service worker closes the notification tagged event-<id>. Apple browsers and Firefox count a push that shows nothing against the site, so there it first replaces that notification in place (same tag, silent) with Dealt with on another device and then closes it; if closing fails, those words remain. The worker also records the event as read locally, so an alert that arrives after its clear is not displayed. Clear pushes carry no computer or session names.

read / handled -> clears_due(sent, not yet cleared, not the reader)
     -> encrypted {"clear":{id,scope}} -> service worker
     -> Apple/Firefox: show(tag=event-id, silent) -> close(tag)
     -> others: close(tag) if shown

Automated checks cover an actual isolated Linux tmux hook, recorded Codex event fixtures, server state and delivery behaviour, and Chromium/WebKit browser flows. They do not establish successful APNs delivery on a physical iPhone/iPad or the complete hook lifecycle on a physical Windows machine. Verify both event kinds on the target computer and the installed Apple app after updating.

Agent adapters normalise their documented hook payloads locally. Configuration is opt-in per agent, merged with existing hooks, and never changes trust or approval decisions. The observer exits successfully even if recording fails, so a notification failure cannot deny a tool. Input and completion support differs as shown above. Codex requires a matching completed-turn record; its unconfirmed Stop is discarded when a later prompt or interruption overtakes it. Other completion hooks are advisory. OpenCode excludes child sessions. Hooks bind only to their inherited tmux pane, including the Windows tmux-compatible terminal; no folder/recency matching is used.

The service worker stores notification display labels locally, learned only from the unlocked account's decrypted owner status. It holds no SSH or vault secret. Labels are cleared on lock, sign-out or disabling notifications. Missing labels fall back to the computer name, then roamd. Push handling is serialised; a failed display acknowledgement cannot create a second generic alert. Requests have a bounded wait. A successful empty inbox suppresses stale alerts on browsers that permit it; WebKit instead shows the neutral activity update required by its visible-push policy. Network failures use the encrypted fallback; unauthenticated fetches use a neutral update.

Browser coverage includes Chromium and WebKit interaction and layout checks and service-worker boundary cases for Apple, Android and desktop browser behaviours. Agent fixtures and isolated real Linux tmux/CLI tests cover adapters and routing. Cross-builds check Windows and both Mac architectures. These are not physical device delivery tests. Android visible delivery was confirmed by the owner; remote Apple delivery still requires confirmation. Optional hooks still need approval on each target machine; automatic activity alerts require the updated machine software but do not require hooks. No claim is made that every agent/browser/OS combination was physically tested. Notification clicks retain their opaque target while locked. After unlock they resolve against the owner's status; unavailable targets open the inbox rather than silently falling through to an unrelated page.