task-banner
Show a large, always-on-top desktop banner when Claude Code finishes a turn — with a one-line summary of what was actually completed. Works even when macOS Notification Center silently drops osascript display notification calls (a common problem: the CLI has no Notification Center registration, so the command exits 0 but nothing shows).
The banner is a borderless Cocoa overlay window drawn by mac-overlay.js (a JXA script adapted from peon-ping), so no notification permissions are needed.
Requirements
- macOS (the overlay uses Cocoa via JXA /
osascript -l JavaScript) - Claude Code with hooks support
Setup
Copy
mac-overlay.jsfrom this skill directory to~/.claude/scripts/mac-overlay.js.Merge a
Stophook into~/.claude/settings.json(read the file first and preserve existing hooks):
{
"hooks": {
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "m=$(cat /tmp/claude-banner-msg 2>/dev/null); rm -f /tmp/claude-banner-msg; [ -f ~/.claude/banner.off ] || osascript -l JavaScript ~/.claude/scripts/mac-overlay.js \"${m:-Task done}\" blue \"\" 0 4 2>/dev/null || true",
"async": true
}
]
}
]
}
}
- To make the banner show what was finished (instead of the generic "Task done"), add this to your global agent instructions file (
~/.claude/CLAUDE.mdorAGENTS.md):
After finishing each task, write a short summary of what was completed to
/tmp/claude-banner-msg(e.g.printf 'Typecheck fixed and committed' > /tmp/claude-banner-msg). Identify the actual task; never use a generic "task done".
The Stop hook displays the message and deletes the file. If the file is absent, it falls back to "Task done".
Toggle (mute / unmute)
The hook checks a sentinel file — no config editing needed:
touch ~/.claude/banner.off # mute the banner
rm -f ~/.claude/banner.off # unmute
When the user asks to mute/unmute the banner, run the matching command and report the resulting state.
How it works / notes
mac-overlay.js <message> <color> <icon> <slot> <dismiss_seconds>— color isblue/yellow/red, dismiss0means persistent-until-click.- The hook consumes
/tmp/claude-banner-msgeven while muted, so no stale summary pops up later when unmuted. - On recent macOS the JXA bridge constant
NSTextAlignmentCenterevaluates to the legacy AppKit value (2), which the unified runtime treats as right-aligned — the bundled script hardcodes alignment1(center) to compensate. /tmp/claude-banner-msgis shared across parallel Claude sessions; if two finish in the same instant one banner may show the other's summary. Use a per-session file path if that ever matters.