Repository navigation
Add Claude Code Integration Guide + Fix Badge Priority Bug #3040
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| --- | ||
| sidebar_position: 1.9 | ||
| id: "claude-code" | ||
| title: "Claude Code Integration" | ||
| --- | ||
|
|
||
| import { VersionBadge } from "@site/src/components/versionbadge"; | ||
|
|
||
| # Claude Code Tab Badges <VersionBadge version="v0.14.2" /> | ||
|
|
||
| When you run multiple Claude Code sessions in parallel — one per feature, one per repo, a few long-running tasks — it gets hard to know which tabs need your attention without clicking through each one. Wave's badge system solves this: hooks in Claude Code write a small visual indicator to the tab header whenever something important happens, so you can see at a glance which sessions are waiting, done, or in trouble. | ||
|
|
||
| :::info | ||
| tl;dr You can copy and paste this page directly into Claude Code and it will help you set everything up! | ||
| ::: | ||
|
|
||
| ## How it works | ||
|
|
||
| Claude Code supports [lifecycle hooks](https://code.claude.com/docs/en/hooks) — shell commands that run automatically at specific points in a session. Wave's `wsh badge` command sets or clears a visual indicator on the current block or tab. By wiring these together, you get ambient awareness across all your sessions without watching any of them. | ||
|
|
||
| Badges auto-clear when you focus the block, so they're purely a "hey, look over here" signal. Once you click in and read what's happening, the badge disappears on its own. | ||
|
|
||
| Wave already shows a bell icon when a terminal outputs a BEL character. These hooks complement that with semantic badges — *permission needed*, *done* — that survive across tab switches and work across splits. | ||
|
|
||
| ### Badge rollup | ||
|
|
||
| If a tab has multiple terminals (block), Wave shows the highest-priority badge on the tab header. Ties at the same priority go to the earliest badge set, so the most urgent signal from any pane in the tab floats to the top. | ||
|
|
||
| ## Setup | ||
|
|
||
| These hooks go in your global Claude Code settings so they apply to every session on your machine, not just one project. | ||
|
|
||
| Add the following to `~/.claude/settings.json`. If you already have a `hooks` key, merge the entries in: | ||
|
|
||
| ```json | ||
| { | ||
| "hooks": { | ||
| "Notification": [ | ||
| { | ||
| "matcher": "permission_prompt", | ||
| "hooks": [ | ||
| { | ||
| "type": "command", | ||
| "command": "wsh badge bell-exclamation --color '#e0b956' --priority 20 --beep" | ||
| } | ||
| ] | ||
| }, | ||
| { | ||
| "matcher": "elicitation_dialog", | ||
| "hooks": [ | ||
| { | ||
| "type": "command", | ||
| "command": "wsh badge message-question --color '#e0b956' --priority 20 --beep" | ||
| } | ||
| ] | ||
| } | ||
| ], | ||
| "Stop": [ | ||
| { | ||
| "hooks": [ | ||
| { | ||
| "type": "command", | ||
| "command": "wsh badge check --color '#58c142' --priority 10" | ||
| } | ||
| ] | ||
| } | ||
| ], | ||
| "PreToolUse": [ | ||
| { | ||
| "matcher": "AskUserQuestion", | ||
| "hooks": [ | ||
| { | ||
| "type": "command", | ||
| "command": "wsh badge message-question --color '#e0b965' --priority 20 --beep" | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| That's it. Restart any running Claude Code sessions for the hooks to take effect. | ||
|
|
||
| :::warning Known Issue | ||
| There is a known issue in Claude Code where `Notification` hooks may be delayed by several seconds before firing. This delay is unrelated to Wave — it occurs in Claude Code itself. See [#5186](https://github.com/anthropics/claude-code/issues/5186) and [#19627](https://github.com/anthropics/claude-code/issues/19627) for details. | ||
| ::: | ||
|
|
||
| ## What each hook does | ||
|
|
||
| ### Permission prompt — `bell-exclamation` gold, priority 20 | ||
|
|
||
| Claude Code occasionally needs your approval before it can continue — to run a command, write a file outside the project, or use a tool that requires explicit permission. When it hits one of these, it stops and waits. Without a signal, you might not notice for minutes. | ||
|
|
||
| This hook fires on the `permission_prompt` notification type and sets a high-priority gold badge with an audible beep. Priority 20 means it beats any other badge on that tab, so a waiting session always surfaces above a finished one. | ||
|
|
||
| When you click into the tab and approve or deny the request, the badge clears automatically. | ||
|
|
||
| ### Session complete — `check` green, priority 10 | ||
|
|
||
| When Claude Code finishes responding, this hook sets a green check badge. It's a low-key signal: glance at the tab bar, see which sessions are done, review their output in whatever order you like. | ||
|
|
||
| ### AskUserQuestion — `message-question` gold, priority 20 | ||
|
|
||
| When Claude Code uses the `AskUserQuestion` tool, it's paused and waiting for you to respond before it can proceed. This `PreToolUse` hook fires just before that tool call and sets the same high-priority gold badge as the permission prompt. | ||
|
|
||
| `PreToolUse` hooks can match any tool by name, so you can add badges for other tools as well — for example, to get a signal whenever Claude runs a shell command (`Bash`) or edits a file (`Edit`). Any tool name Claude Code supports can be used as a matcher. | ||
|
|
||
| ## Choosing your own icons and colors | ||
|
|
||
| Icon names are [Font Awesome](https://fontawesome.com/icons) icon names without the `fa-` prefix. Colors are any valid CSS color — hex values, named colors, or anything else CSS accepts. | ||
|
|
||
| Some icon and color ideas: | ||
|
|
||
| | Situation | Icon | Color | | ||
| |-----------|------|-------| | ||
| | Custom high-priority alert | `triangle-exclamation` | `#FF453A` | | ||
| | Blocked / waiting on input | `hourglass-half` | `#FF9500` | | ||
| | Neutral / informational | `circle-info` | `#429DFF` | | ||
| | Background task running | `spinner` | `#00FFDB` | | ||
|
|
||
| See the [`wsh badge` reference](/reference/wsh-cmd/badge) for all available flags. | ||
|
|
||
| ## Adjusting priorities | ||
|
|
||
| Priority controls which badge wins when multiple blocks in a tab each have one. Higher numbers take precedence. The defaults above use: | ||
|
|
||
| - **20** for permission prompts — always surfaces above everything else | ||
| - **10** for session complete — visible when nothing more urgent is active | ||
|
|
||
| If you add more hooks, keep permission-blocking signals at the high end (15–25) and informational signals at the low end (5–10). | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -123,8 +123,7 @@ function getTabBadgeAtom(tabId: string, env?: TabBadgesEnv): Atom<Badge[]> { | |
| } | ||
| const tabOref = WOS.makeORef("tab", tabId); | ||
| const tabBadgeAtom = getBadgeAtom(tabOref); | ||
| const tabAtom = | ||
| env != null ? env.wos.getWaveObjectAtom<Tab>(tabOref) : WOS.getWaveObjectAtom<Tab>(tabOref); | ||
| const tabAtom = env != null ? env.wos.getWaveObjectAtom<Tab>(tabOref) : WOS.getWaveObjectAtom<Tab>(tabOref); | ||
| rtn = atom((get) => { | ||
| const tab = get(tabAtom); | ||
| const blockIds = tab?.blockids ?? []; | ||
|
|
@@ -213,7 +212,13 @@ function setupBadgesSubscription() { | |
| } | ||
| return; | ||
| } | ||
| globalStore.set(curAtom, data.clear ? null : (data.badge ?? null)); | ||
| if (data.clear) { | ||
| globalStore.set(curAtom, null); | ||
| } else if (data.badge != null) { | ||
| const existing = globalStore.get(curAtom); | ||
| const candidates = existing != null ? [existing, data.badge] : [data.badge]; | ||
| globalStore.set(curAtom, sortBadges(candidates)[0]); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The client subscription still forgets suppressed badges.
🤖 Prompt for AI Agents |
||
| } | ||
| }, | ||
| }); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -94,8 +94,12 @@ func setBadge(oref waveobj.ORef, data baseds.BadgeEvent) { | |
| delete(globalBadgeStore.transient, orefStr) | ||
| log.Printf("badge store: badge cleared: oref=%s\n", orefStr) | ||
| } else { | ||
| globalBadgeStore.transient[orefStr] = *data.Badge | ||
| log.Printf("badge store: badge set: oref=%s badge=%+v\n", orefStr, *data.Badge) | ||
| incoming := *data.Badge | ||
| existing, hasExisting := globalBadgeStore.transient[orefStr] | ||
| if !hasExisting || incoming.Priority > existing.Priority || (incoming.Priority == existing.Priority && incoming.BadgeId > existing.BadgeId) { | ||
| globalBadgeStore.transient[orefStr] = incoming | ||
| log.Printf("badge store: badge set: oref=%s badge=%+v\n", orefStr, incoming) | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't collapse per-badge state into a single stored badge.
🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.