To make Gemini CLI notify you when it's done, set general.enableNotifications to true in ~/.gemini/settings.json for a terminal alert, or add an AfterAgent hook in the same file that runs a toast, sound or curl command. AfterAgent fires when the agent loop ends, and Notification fires when a tool call is waiting for your approval. Hooks are on by default, so there is no flag to flip. To wire both to your desktop and phone at once, run npx anotifier@latest setup. Here is every option, then fixes for hooks that don't fire. On Antigravity CLI instead? Its hooks use a different format; see the Antigravity CLI guide.
Rather skip the manual setup? One command wires every agent you have installed to desktop toasts, phone push, and Slack, Discord or Telegram:
Updated 2026-10-16 · also see the Gemini CLI overview and the full docs
| You want | Do this |
|---|---|
| A terminal alert, no scripts | Set general.enableNotifications to true (Option 1) |
| A desktop toast when a run finishes | Add an AfterAgent hook (Option 2, commands per OS) |
| An alert when a tool waits for approval | Add a Notification hook, or turn on the built-in alert |
| A sound when it's done | Set general.notificationMethod to bell, or play a sound file from an AfterAgent hook (sounds) |
| A push to your phone | A hook that posts to ntfy (phone), or anotifier |
| All of it in one command | npx anotifier@latest setup (Option 3) |
Gemini CLI can alert you from the terminal itself, but the setting is off by default. Turn it on in ~/.gemini/settings.json:
{
"general": {
"enableNotifications": true,
"notificationMethod": "auto"
}
}Gemini CLI describes it as terminal notifications for action-required prompts and session completion: one alert when a response finishes and one when something needs your action, such as a tool approval. When your terminal reports focus changes, Gemini CLI stays quiet while its window is in front. notificationMethod takes auto, osc9, osc777 or bell. With auto, iTerm2 gets an OSC 9 notification; Alacritty, Apple Terminal, the VS Code terminal and Windows Terminal get the terminal bell; every other terminal gets OSC 777.
What you get: an alert that reaches your terminal, not your phone, with fixed text such as "Gemini CLI session complete". For a sound, set notificationMethod to bell and check what your terminal does with a bell. In Windows Terminal that is the bellStyle profile setting, which accepts audible (the default), window, taskbar, all and none.
Gemini CLI reads hooks from the hooks key of settings.json, not from a separate hooks file. Hooks are enabled by default (hooksConfig.enabled is true, and changing it needs a restart). Each event holds a list of groups, and each group holds a hooks list. Only type and command are required; name, timeout (milliseconds, 60000 by default) and description are optional. This shape runs a toast when the agent finishes and a louder one when a tool needs approval:
{
"hooks": {
"AfterAgent": [
{
"hooks": [
{
"name": "notify-done",
"type": "command",
"command": "notify-send \"Gemini CLI\" \"Run complete\"",
"timeout": 10000
}
]
}
],
"Notification": [
{
"hooks": [
{
"name": "notify-approval",
"type": "command",
"command": "notify-send -u critical \"Gemini CLI\" \"Needs your approval\"",
"timeout": 10000
}
]
}
]
}
}| Event | Fires when | Use it for |
|---|---|---|
AfterAgent | The agent loop ends | The run-finished alert |
Notification | A tool call is waiting for your confirmation (notification_type is ToolPermission) | The needs-approval alert. It never fires for a run that needs no approval |
SessionEnd | The CLI exits or a session is cleared | Not for alerts: Gemini CLI doesn't wait for it to finish |
Gemini CLI writes one JSON object to the hook's stdin. Every event carries session_id, transcript_path, cwd, hook_event_name and timestamp. AfterAgent adds prompt, prompt_response and stop_hook_active; Notification adds notification_type, message and details. The values below are illustrative:
{
"session_id": "string",
"transcript_path": "/absolute/path/to/session.json",
"cwd": "/home/you/my-app",
"hook_event_name": "AfterAgent",
"timestamp": "2026-10-16T09:41:07.000Z",
"prompt": "your original request",
"prompt_response": "the final text the agent wrote",
"stop_hook_active": false
}{
"session_id": "string",
"transcript_path": "/absolute/path/to/session.json",
"cwd": "/home/you/my-app",
"hook_event_name": "Notification",
"timestamp": "2026-10-16T09:41:07.000Z",
"notification_type": "ToolPermission",
"message": "a summary of the alert",
"details": {
"…": "metadata such as the tool name or file path"
}
}A hook can read cwd to put the project name in the alert. This script needs jq; save it as ~/.gemini/hooks/notify.sh, run chmod +x on it, and set the hook's command to its path:
#!/bin/bash
input=$(cat)
project=$(basename "$(echo "$input" | jq -r '.cwd')")
notify-send "Gemini CLI · $project" "Run complete" || true{}.0 for success. Exit 1 is a warning. Any other code that prints text, such as 127 for a missing command, is treated as a block, and on AfterAgent it makes the agent retry. End the script with || true.timeout. Keep the command short.GEMINI_PROJECT_DIR, GEMINI_CWD and GEMINI_SESSION_ID.bash -c on Linux and macOS. On Windows they run through PowerShell: pwsh if it is installed, otherwise Windows PowerShell 5.1.Put these in the command field of the AfterAgent hook. Inside the JSON, write each double quote as \" and use forward slashes in paths. Chain a toast and a sound with ;, which works in both bash and PowerShell. In bash, end the command with || true: a failing command that prints text, such as a missing notify-send (exit 127), would otherwise make AfterAgent retry.
| OS | Toast | Sound |
|---|---|---|
| macOS | osascript -e 'display notification "Run complete" with title "Gemini CLI"' | afplay /System/Library/Sounds/Glass.aiff |
| Linux | notify-send "Gemini CLI" "Run complete" | paplay /usr/share/sounds/freedesktop/stereo/complete.oga |
| Windows | A PowerShell toast script, below | (New-Object Media.SoundPlayer 'C:/Windows/Media/chimes.wav').PlaySync() |
osascript are attributed to Script Editor, and if Script Editor isn't allowed to send notifications the command fails silently. afplay plays any .aiff, .mp3 or .wav.paplay needs PulseAudio or PipeWire and the freedesktop sound theme. notify-send has nowhere to deliver on a server or over SSH with no desktop session.PlaySync waits for the sound to end so PowerShell doesn't exit first. Any .wav in C:\Windows\Media works.notify-send and afplay act on the remote machine, so use phone push there.For a Windows toast, save this as C:\Users\you\.gemini\win-toast.ps1. It needs no module and runs on Windows PowerShell 5.1; it is the same toast as in the WSL toast guide:
param(
[string]$Title = 'Codex',
[string]$Message = 'Done'
)
$ErrorActionPreference = 'Stop'
$null = [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime]
$null = [Windows.UI.Notifications.ToastNotification, Windows.UI.Notifications, ContentType = WindowsRuntime]
$AppId = '{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\WindowsPowerShell\v1.0\powershell.exe'
$xml = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02)
$texts = $xml.GetElementsByTagName('text')
$null = $texts.Item(0).AppendChild($xml.CreateTextNode($Title))
$null = $texts.Item(1).AppendChild($xml.CreateTextNode($Message))
$toast = [Windows.UI.Notifications.ToastNotification]::new($xml)
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($AppId).Show($toast)Then point the hooks at it. The path uses forward slashes, so the JSON needs no backslash escapes:
{
"hooks": {
"AfterAgent": [
{
"hooks": [
{
"type": "command",
"command": "powershell.exe -NoProfile -ExecutionPolicy Bypass -File \"C:/Users/you/.gemini/win-toast.ps1\" -Title \"Gemini CLI\" -Message \"Run complete\""
}
]
}
],
"Notification": [
{
"hooks": [
{
"type": "command",
"command": "powershell.exe -NoProfile -ExecutionPolicy Bypass -File \"C:/Users/you/.gemini/win-toast.ps1\" -Title \"Gemini CLI\" -Message \"Needs your approval\""
}
]
}
]
}
}The toast shows under the name Windows PowerShell with the title and message you pass, and no agent icon, custom sound or click-to-focus. If nothing appears, Do not disturb (Focus Assist) may be on.
ntfy turns an HTTP POST into a phone push. Install the ntfy app, subscribe to a long random topic name, and have the hooks post to it. -s -o /dev/null keeps curl's reply off the hook's stdout:
{
"hooks": {
"AfterAgent": [
{
"hooks": [
{
"type": "command",
"command": "curl -s -o /dev/null -H 'Title: Gemini CLI' -d 'Run complete' https://ntfy.sh/your-long-random-topic"
}
]
}
],
"Notification": [
{
"hooks": [
{
"type": "command",
"command": "curl -s -o /dev/null -H 'Title: Gemini CLI' -H 'Priority: urgent' -d 'Needs your approval' https://ntfy.sh/your-long-random-topic"
}
]
}
]
}
}On Windows, Gemini CLI runs the hook in PowerShell. In Windows PowerShell 5.1, curl is an alias for Invoke-WebRequest; use curl.exe and -o NUL in either shell instead of -o /dev/null. For the full setup, including private topics, self-hosting and priorities, see the ntfy phone push guide and the phone page.
npx anotifier@latest setupThe wizard finds a Gemini CLI install in ~/.gemini, registers AfterAgent and Notification hooks in settings.json with a 30-second timeout, and backs the file up first. It also removes anotifier's entries from a stale ~/.gemini/hooks.json left by older versions. A finished run arrives as my-app · Gemini with the text "Task complete"; a tool waiting for approval arrives urgent as "Needs your input", on your desktop and phone. The text is generic for Gemini CLI: showing the agent's own words is Claude Code-only today. Gemini CLI is driven end to end in CI on Linux and macOS with a headless gemini -p run, and the job fails unless the AfterAgent hook delivers a real push.
| Feature | Built-in | DIY hook | anotifier |
|---|---|---|---|
| Desktop toast | Terminal-dependent | Yes, fixed text | Yes, Windows · macOS · Linux · WSL |
| Phone push | No | With a curl to ntfy | Yes, ntfy, generic by default |
| Slack / Discord / Telegram | No | Write the JSON yourself | Yes, pick a format |
| Shows what the agent said | No | No | Yes for Claude Code (toast + webhook) |
| Names the project | No | Hard-code it | Yes, my-app · Claude Code in every title |
| Urgent for approvals, calm for done | No | Two hooks, two scripts | Yes, per-event priority |
| Click to focus the window | No | No | Yes (Windows) |
| Every agent, one config | No | Repeat per agent | Yes |
| Snooze / quiet hours | No | No | Yes |
| Setup time | 1 setting | 10–20 min per agent | One command |
anotifier is free, open source (AGPL-3.0), has zero dependencies, and uninstalls cleanly with anotifier uninstall. Full reference in the docs.
npx anotifier@latest uninstall removes only the hooks anotifier added and leaves your own. anotifier snooze 2h silences every channel for two hours. For the Gemini CLI page and what setup writes, see Gemini CLI.
Gemini CLI merges settings from several files. For a single value the order is system defaults (system-defaults.json), then user (~/.gemini/settings.json), then project (.gemini/settings.json in the current directory), then the system settings.json (/etc/gemini-cli/settings.json on Linux, C:\ProgramData\gemini-cli\settings.json on Windows), which overrides all of them. Hook lists are the exception: they are concatenated, so a hook in two files runs twice.
| Symptom | Cause and fix |
|---|---|
| No hook fires | hooksConfig.enabled set to false turns every hook off, and the change needs a restart. Set it to true or delete it, then restart. Run /hooks panel to see what Gemini CLI loaded. |
| The hook is listed but disabled | Its name (or its command, if it has no name) is in hooksConfig.disabled, or you ran /hooks disable. Run /hooks enable <name> or /hooks enable-all. |
| A new hook isn't listed | Gemini CLI discards a malformed hook definition. Check the JSON is valid, each event holds a list of groups, each group has a hooks list, and each hook has type set to command plus a command. |
| No settings hook runs in one folder | Folder Trust is on and the folder is untrusted, so Gemini CLI loads no settings hooks at all, user ones included. Trust the folder with /permissions, or turn off security.folderTrust.enabled. |
| It fires twice | The same hook is in two settings files, or a hand-written hook sits beside anotifier's. anotifier collapses a repeat of its own hook within 1.5 seconds but can't de-duplicate another script. |
Notification never fires | It fires only when a tool call waits for your confirmation. A run that needs no approval sends only AfterAgent. |
| Gemini CLI pauses after a run | Hooks run synchronously. Keep the command short, or lower its timeout. |
| Text from the hook appears in the session | Plain text on stdout becomes a system message. Print nothing, and use curl -s -o /dev/null. |
| The command works in a terminal but not in a Windows hook | Windows hooks run in PowerShell (pwsh if installed, otherwise 5.1), not cmd. In 5.1, curl is an alias for Invoke-WebRequest, so use curl.exe. Use forward slashes in paths. |
Google replaced Gemini CLI with Antigravity CLI on June 18, 2026: Gemini CLI stopped serving Google AI Pro and Ultra subscribers and free users. It stays available with a paid Gemini API key or a Code Assist Standard or Enterprise license, and everything above applies to it.
Antigravity CLI has its own hooks, in ~/.gemini/config/hooks.json, with different events (SessionStart, PreToolUse, PostToolUse, PreInvocation, PostInvocation and Stop). There is no AfterAgent or Notification event there, so the hooks above don't fire. Antigravity CLI does have its own built-in notifications setting (default false), a desktop notification and terminal bell for finished tasks, permission prompts and questions; the Antigravity CLI notifications guide covers it and the hook you write yourself. anotifier 1.4.0 and later wires Antigravity separately, with a Stop handler that sends "Task complete" and no needs-input alert, because Antigravity hooks have no such event; it has not yet been tested against a live Antigravity CLI. What setup writes is on the Antigravity CLI page.
Each Gemini CLI claim above comes from the official docs or source code, read on 2026-10-16 against Gemini CLI 0.64.0, the latest release at the time. Settings and hooks change between releases, so confirm anything you rely on there.
general.*, hooksConfig.*, hooks.*): docs/reference/configuration.mdbellStyle: Microsoft's advanced profile settingsSet general.enableNotifications to true in ~/.gemini/settings.json for a terminal alert, or add an AfterAgent hook to the same file that runs notify-send, osascript or a PowerShell toast. For desktop and phone together, run npx anotifier@latest setup, which registers the hooks and sends the alert to your toast and ntfy.
AfterAgent, when the agent loop ends. Its stdin JSON has prompt, prompt_response and stop_hook_active plus session_id, transcript_path, cwd, hook_event_name and timestamp. Notification is a different event: it fires only when a tool call is waiting for your approval. Both are configured under hooks in ~/.gemini/settings.json.
Yes, but they are off by default. Set general.enableNotifications to true and Gemini CLI sends a terminal notification when a response finishes and when something needs your action. general.notificationMethod picks auto, osc9, osc777 or bell, and the alert reaches your terminal, not your phone.
Set general.notificationMethod to bell with notifications enabled, or add an AfterAgent hook that plays a file: afplay /System/Library/Sounds/Glass.aiff on macOS, paplay /usr/share/sounds/freedesktop/stereo/complete.oga on Linux, or a Media.SoundPlayer call in PowerShell on Windows. The hook runs where Gemini CLI runs, so over SSH it plays on the remote machine.
No, hooks are on by default; hooksConfig.enabled is true unless you set it to false, and changing it needs a restart. If a hook doesn't fire, run /hooks panel to see whether it loaded and is enabled, check hooksConfig.disabled, and remember that in an untrusted folder, with Folder Trust turned on, Gemini CLI loads no settings hooks at all, user ones included.
Yes, through a hook. Add an AfterAgent hook that runs curl -s -o /dev/null -d 'Run complete' against your ntfy topic, and subscribe to the topic in the ntfy app. anotifier does the same without writing the hook: its setup asks about phone notifications and registers AfterAgent and Notification for you.
Not the Gemini CLI hooks: Antigravity has no AfterAgent or Notification event, and keeps its hooks in ~/.gemini/config/hooks.json. anotifier 1.4.0 and later adds an anotifier group with a Stop handler there, so you get a "Task complete" alert; anotifier sends no needs-input alert, because Antigravity hooks have no such event, and it has not yet been tested against a live Antigravity CLI. Antigravity's own notifications setting covers permission prompts and questions. Gemini CLI itself still works with a paid Gemini API key or a Code Assist Standard or Enterprise license.
One command wires Gemini CLI and every other agent you have installed.
npx anotifier@latest setup