Settings → Notifications has independent switches for banners and sounds. Both are off on a new installation. Sound-only mode does not request notification permission. Choose which events to announce: response ready, approval needed, input needed, errors and limits. These choices remain editable when both channels are off.
Completion uses Lunavect · Lift, an original 0.46-second rising tone with a soft onset and a short glass-like decay. Play sound previews it without changing notification settings. Approval and input events use the system attention sound. If banners are enabled, clicking a notification opens its session without waiting for a list refresh; if the session is no longer listed, the session panel explains why.
Notifications follow newly observed state transitions. A Claude reply that ends while Claude’s own background tasks are still running is not announced: the notification arrives once, after the last task has finished and Claude has stopped with nothing left to wait for (see Background tasks in Claude). Re-reading the same state does not replay a sound, and the initial session list does not generate historical completion alerts. A session that starts while Lunavect runs is announced even if it already waits or has finished by the time Lunavect first reads it. A reply or error is announced once even if a client’s session list briefly reports the session as busy again. An approval request is announced once: Claude’s session list reporting the same wait does not announce it again as input needed, and a declined request ends the wait with the session’s next step (see Permission requests). A hidden session stays silent until a new task shows it again. Closing a session is not a reply: when the client reports the end of a session (SessionEnd) while it was still working or waiting, for example after Esc and /exit, no Response ready is sent. A reply that Claude finished (Stop) right before the session ended, as claude -p and Agent SDK scripts do within milliseconds, is still announced once, whether or not Lunavect read the two events separately. The end reason code is kept for diagnosis only. macOS notification settings can independently silence banner sounds.
When several sessions finish close together, the completion sound plays at most once every five seconds by default. Pause between completion sounds offers no pause, 2, 5, 10 or 30 seconds. Each enabled banner still appears and opens its own session. Notification Center keeps one entry per session: a newer state, such as the reply after an approval request, replaces the session’s earlier banner. Limit notices and test notifications remain separate entries. The same limit applies in sound-only mode and when a failed banner falls back to direct audio. Approval and input sounds are not suppressed by this completion cooldown. Explicit sound previews and test notifications play immediately.
Session limits post their own notices: a session stopped or resting, a continuation that could not be typed, and the question after a usage limit cut a session off, with buttons such as Continue now, +5% and Continue after the reset. They follow the limits you set and General → If a limit cuts work off rather than the switches above (choose Do nothing there to stop the question), and they appear only when macOS allows Lunavect’s notifications; otherwise the Tasks tab shows the same card.
The completion cue is synthesized from oscillators and short reflections, without sampled recordings or external dependencies, by scripts/generate-notification-sound.py. Its source and WAV asset use the project’s MIT license. The WAV is bundled with the app for both notification banners and direct sound-only playback.
When a Claude Code turn ends with an API error, the row names the reason and Errors sends one notification: Limit reached (with the time it is available again when Lunavect knows the exhausted window), Can’t reach Claude, Claude service error, Sign in again, Account issue or Error. The reason comes from the documented StopFailure.error field; a server error counts as a lost connection when its message says so. Only the reason is stored, never the error text. Codex does not report such an event.
Limits warns once when a five-hour or weekly allowance of Claude or Codex drops below the chosen level (5, 10, 20 or 25%, 10% by default) and announces its return at the reset time, but only for a window that was low. Warnings use fresh values only: saved values, values older than 15 minutes and values reported with a source error never cross the threshold. A relaunch does not repeat a warning for the same window. A return is announced when the reset passes, or on wake or relaunch up to an hour later; older returns are not announced. A reset that Claude’s /usage shows to the minute passes at the end of that minute, and when two sources report the same reset, the later time counts. While a window is at 0%, Lunavect asks the provider again before that saved reset only because a manual reset in the Claude or Codex app can lift it early (Codex hourly and after session activity, Claude after a response its limit did not refuse, at most hourly); one confirming request follows shortly after the reset (see when limits are refreshed). Warnings are not used up while limit notifications are off, and a disconnected provider is never announced. When a reached limit is announced with an error, the time shown is when every exhausted window has reset, taken from fresh values only.