anotifier / guides / gemini-cli-notifications
[ GUIDE · GEMINI CLI ]
Gemini CLI logo

How to get notified when Gemini CLI finishes

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:

$ npx anotifier@latest setup

Updated 2026-10-16 · also see the Gemini CLI overview and the full docs

Quick setup: which Gemini CLI alert do you want?

You wantDo this
A terminal alert, no scriptsSet general.enableNotifications to true (Option 1)
A desktop toast when a run finishesAdd an AfterAgent hook (Option 2, commands per OS)
An alert when a tool waits for approvalAdd a Notification hook, or turn on the built-in alert
A sound when it's doneSet general.notificationMethod to bell, or play a sound file from an AfterAgent hook (sounds)
A push to your phoneA hook that posts to ntfy (phone), or anotifier
All of it in one commandnpx anotifier@latest setup (Option 3)

Option 1: Gemini CLI's built-in terminal notifications

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.

Option 2: Gemini CLI hooks in ~/.gemini/settings.json

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
          }
        ]
      }
    ]
  }
}
EventFires whenUse it for
AfterAgentThe agent loop endsThe run-finished alert
NotificationA 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
SessionEndThe CLI exits or a session is clearedNot 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
  • Gemini CLI expects stdout to be JSON, so plain text there becomes a system message in the session. Print nothing, or {}.
  • Exit 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.
  • Gemini CLI runs matching hooks as part of the agent loop and waits for them, up to the hook's timeout. Keep the command short.
  • The hook's environment includes GEMINI_PROJECT_DIR, GEMINI_CWD and GEMINI_SESSION_ID.
  • Hooks run through bash -c on Linux and macOS. On Windows they run through PowerShell: pwsh if it is installed, otherwise Windows PowerShell 5.1.

Gemini CLI desktop toast and sound by OS

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.

OSToastSound
macOSosascript -e 'display notification "Run complete" with title "Gemini CLI"'afplay /System/Library/Sounds/Glass.aiff
Linuxnotify-send "Gemini CLI" "Run complete"paplay /usr/share/sounds/freedesktop/stereo/complete.oga
WindowsA PowerShell toast script, below(New-Object Media.SoundPlayer 'C:/Windows/Media/chimes.wav').PlaySync()
  • macOS notifications from 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.
  • On Windows, PlaySync waits for the sound to end so PowerShell doesn't exit first. Any .wav in C:\Windows\Media works.
  • A hook runs on the machine where Gemini CLI runs. Over SSH, 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.

Gemini CLI alerts on your phone with ntfy

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.

Option 3: anotifier, one command for Gemini CLI and every other agent

npx anotifier@latest setup

The 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.

FeatureBuilt-inDIY hookanotifier
Desktop toastTerminal-dependentYes, fixed textYes, Windows · macOS · Linux · WSL
Phone pushNoWith a curl to ntfyYes, ntfy, generic by default
Slack / Discord / TelegramNoWrite the JSON yourselfYes, pick a format
Shows what the agent saidNoNoYes for Claude Code (toast + webhook)
Names the projectNoHard-code itYes, my-app · Claude Code in every title
Urgent for approvals, calm for doneNoTwo hooks, two scriptsYes, per-event priority
Click to focus the windowNoNoYes (Windows)
Every agent, one configNoRepeat per agentYes
Snooze / quiet hoursNoNoYes
Setup time1 setting10–20 min per agentOne 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 hooks not firing: fixes by symptom

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.

SymptomCause and fix
No hook fireshooksConfig.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 disabledIts 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 listedGemini 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 folderFolder 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 twiceThe 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 firesIt fires only when a tool call waits for your confirmation. A run that needs no approval sends only AfterAgent.
Gemini CLI pauses after a runHooks run synchronously. Keep the command short, or lower its timeout.
Text from the hook appears in the sessionPlain 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 hookWindows 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.

Moved to Antigravity CLI?

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.

Sources

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.

Gemini CLI notification questions

How do I get a notification when Gemini CLI finishes?

Set 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.

Which Gemini CLI hook fires when the agent is done?

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.

Does Gemini CLI have built-in notifications?

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.

How do I make Gemini CLI play a sound when it's done?

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.

Do Gemini CLI hooks need to be enabled?

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.

Can Gemini CLI send alerts to my phone?

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.

Does this work with Antigravity CLI?

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.

Try it

One command wires Gemini CLI and every other agent you have installed.

npx anotifier@latest setup

Other guides