Skip to content

settings: add toggle to disable Claude Code idle notification hook - #4254

Open
yuanzidev wants to merge 2 commits into
manaflow-ai:mainfrom
yuanzidev:claude-notification-hook-toggle
Open

yuanzidev wants to merge 2 commits into
manaflow-ai:mainfrom
yuanzidev:claude-notification-hook-toggle

Conversation

@yuanzidev

@yuanzidev yuanzidev commented May 16, 2026 •

Copy link
Copy Markdown

Summary

  • Add a dedicated automation.claudeCodeNotificationHook setting (default true) that controls whether the Claude Code Notification hook is injected, independently of the main automation.claudeCodeIntegration toggle
  • When disabled, only the idle/waiting-for-input notification is omitted — completion notifications (Stop hook), session tracking, Feed, status, and permission hooks continue working

Motivation

Claude Code turns can produce two notifications: a completion notification from the Stop hook, and a second notification from the Notification hook when Claude sits idle at the prompt. The second notification arrives roughly a minute after completion and feels like a duplicate for users who mainly want to know when work is finished. Currently there is no way to disable only the idle notification while keeping completion notifications.

Changes

File Change
Sources/cmuxApp.swift Add notificationHookEnabledKey/accessor to ClaudeCodeIntegrationSettings, @AppStorage binding, UI toggle in Automation settings, reset defaults
Sources/GhosttyTerminalView.swift Export CMUX_CLAUDE_NOTIFICATION_HOOK_DISABLED=1 env var when setting is disabled
Resources/bin/claude Split HOOKS_JSON into base/notification/tail parts, conditionally assemble based on env var
Sources/KeyboardShortcutSettingsFileStore.swift Parse claudeCodeNotificationHook from JSONC config
Sources/CmuxSettingsJSONPathSupport.swift Register automation.claudeCodeNotificationHook as a supported JSON path
Sources/SettingsNavigation.swift Add navigation anchor for the new setting
Sources/SettingsSearchAliases.swift Add search alias
Resources/Localizable.xcstrings Add localized strings (en, ja)

Settings UI

The toggle is placed in Settings → Automation, directly below the existing Claude Code Integration toggle. It is disabled and dimmed when Claude Code Integration is turned off.

Label: Claude Code Idle Notifications
Subtitle: Notify when Claude Code reports that it is waiting for input. Completion notifications still use the Stop hook.

Config file

// cmux.json
{
  "automation": {
    "claudeCodeIntegration": true,
    "claudeCodeNotificationHook": false  // disable idle notifications, keep completion notifications
  }
}

Test plan

  • Verify the new toggle appears in Settings → Automation when Claude Code Integration is enabled
  • Verify the toggle is disabled/dimmed when Claude Code Integration is off
  • Verify default behavior is unchanged (setting defaults to true)
  • Verify disabling the toggle removes the Notification hook from the injected HOOKS_JSON while preserving all other hooks
  • Verify Stop hook completion notifications still fire when the toggle is off
  • Verify session tracking, Feed, status, and permission hooks still work
  • Verify automation.claudeCodeIntegration=false still disables the entire integration
  • Verify the setting takes effect from cmux.json
  • Verify Reset All Defaults resets the new toggle to true

Summary by cubic

Adds a toggle to disable Claude Code’s idle “waiting for input” notifications without affecting completion notifications or other hooks. This reduces duplicate pings while keeping the integration intact.

  • New Features
    • New setting automation.claudeCodeNotificationHook (default true) to control the Notification hook independently of automation.claudeCodeIntegration.
    • UI toggle in Settings → Automation; dimmed when the integration is off; added to settings search and navigation.
    • When disabled, cmux sets CMUX_CLAUDE_NOTIFICATION_HOOK_DISABLED=1 and the claude shim omits the Notification hook; completion notifications, session tracking, Feed, status, and permission hooks continue to work.
    • Config support via cmux.json (e.g., "claudeCodeNotificationHook": false); included in Reset All Defaults; localized strings added and user-facing copy updated to avoid internal hook names.

Written for commit e551e59. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • New Features

    • Added a toggle in Automation settings to enable "Claude Code Idle Notifications" (visibly disabled when Claude hooks are off). Included in "Reset All Settings".
    • Settings entry is discoverable via Settings search with a new alias.
  • Localization

    • Added localized strings for the new setting and its subtitle across multiple locales.

Review Change Stack

Users can now disable Claude Code's idle/waiting-for-input notification
(Notification hook) while keeping completion notifications (Stop hook),
avoiding duplicate notifications. New setting automation.claudeCodeNotificationHook
defaults to true (existing behavior).
@vercel

vercel Bot commented May 16, 2026

Copy link
Copy Markdown

@yuanzidev is attempting to deploy a commit to the Manaflow Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented May 16, 2026 •

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: dbe93394-5a5b-4c46-9574-08d87b1c369d

📥 Commits

Reviewing files that changed from the base of the PR and between 7772ef4 and e551e59.

📒 Files selected for processing (2)
  • Resources/Localizable.xcstrings
  • Sources/cmuxApp.swift

📝 Walkthrough

Walkthrough

This PR adds a Claude Code "idle notifications" toggle that lets users enable or disable notification hooks when Claude reports it is waiting for input. It includes localized UI labels, a settings model backed by UserDefaults, settings file parsing/validation and search/navigation wiring, and conditional hook injection into the Claude wrapper runtime.

Changes

Claude Code Idle Notification Hook

Layer / File(s) Summary
Localized UI labels and search aliases
Resources/Localizable.xcstrings
Adds settings.automation.claudeCode.notificationHook, settings.automation.claudeCode.notificationHook.subtitle, and settings.search.alias.setting.automation.claude-notification-hook with multi-locale localizations.
Settings model, defaults, and toggle UI
Sources/cmuxApp.swift
Adds ClaudeCodeIntegrationSettings.notificationHookEnabledKey and defaultNotificationHookEnabled, notificationHookEnabled(defaults:), an @AppStorage binding, a settings toggle row (disabled/dimmed when hooks are off) with accessibility id, and reset behavior.
Settings parsing, validation, navigation, and aliases
Sources/CmuxSettingsJSONPathSupport.swift, Sources/KeyboardShortcutSettingsFileStore.swift, Sources/SettingsNavigation.swift, Sources/SettingsSearchAliases.swift
Allows automation.claudeCodeNotificationHook in supported JSON paths, parses the automation setting into managed defaults, registers a SettingsSearch entry and anchor mapping, and adds a search alias mapping.
Claude wrapper and terminal hook injection
Resources/bin/claude, Sources/GhosttyTerminalView.swift
Refactors hook JSON into base/notification/tail fragments and conditionally omits the Notification hook when CMUX_CLAUDE_NOTIFICATION_HOOK_DISABLED=1; Ghostty terminal view injects CMUX_CLAUDE_NOTIFICATION_HOOK_DISABLED when the setting is disabled.

Sequence Diagram(s)

sequenceDiagram
  participant ComponentA
  participant ComponentB
  ComponentA->>ComponentB: observable interaction
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly Related PRs

  • manaflow-ai/cmux#821: Also changes Claude wrapper hook JSON and adds a UserPromptSubmit hook affecting hook emissions.
  • manaflow-ai/cmux#3418: Related edits to Claude --settings/notification-hook wiring and runtime notification behavior.
  • manaflow-ai/cmux#3298: Modifies the Claude hook wrapper emission style similar to this PR's HOOKS_JSON refactor.

Poem

🐰 I hop through strings and toggle light,

Claude waits—now silent or bright,
A switch in Settings, labels clear,
Hooks injected or held back here,
Locales sing and terminals sigh—hooray, no idle cry!


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Cmux Swift Actor Isolation ❓ Inconclusive No result was produced after verification. Marking as INCONCLUSIVE. Re-run the check or adjust instructions to produce a final result.
✅ Passed checks (14 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and specifically describes the main change: adding a toggle setting to disable Claude Code idle notification hook, which aligns with the primary objective of the changeset.
Description check ✅ Passed The PR description comprehensively covers the required template sections: Summary (what changed and why), Testing (detailed test plan with checkboxes), and includes motivation, detailed file-by-file changes, UI placement, config examples, and bot review trigger block. All key information is present and well-structured.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Blocking Runtime ✅ Passed No blocking/timing patterns found. Changes are pure configuration: UserDefaults storage, SwiftUI bindings, environment variables, and data registration.
Cmux No Hacky Sleeps ✅ Passed No sleep(), usleep(), setTimeout, setInterval, polling, or fixed-delay sync code found in non-Swift runtime files. Resources/bin/claude changes are configuration/conditional logic only.
Cmux Swift Concurrency ✅ Passed No legacy async patterns. Uses @AppStorage, UserDefaults accessors, and synchronous code. No DispatchQueue, Combine, completion handlers, or fire-and-forget Tasks.
Cmux Swift @Concurrent ✅ Passed No violations found. PR adds only synchronous functions and does not violate swift-concurrent-annotation.md rules: no nonisolated async, invalid @concurrent, or unsafely isolated work.
Cmux Swift File And Package Boundaries ✅ Passed Changes comply: new files under 400 lines, additions to existing files are minimal (+3, +27 lines), and code is inherently app-target UI/state within allowed exceptions.
Cmux Swift Logging ✅ Passed No logging violations found. All 40 new lines of Swift code are configuration, UI, or parsing code without print, NSLog, debugPrint, or other forbidden logging.
Cmux User-Facing Error Privacy ✅ Passed User strings use safe product terminology without exposing environment variables, internal flags, or hook names to end users.
Cmux Swiftui State Layout ✅ Passed PR uses @AppStorage (correct pattern) instead of ObservableObject/@published. No violations: no new state classes, no GeometryReader changes, no store refs in lazy rows, no render-time mutations.
Cmux Architecture Rethink ✅ Passed Clean settings toggle addition. Single owner, single UserDefaults source, no duplicate wiring, no timing patterns. Follows existing pattern, no violations.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR adds Claude Code notification hook toggle to existing Settings view; does not create new standalone windows, panels, NSWindowController, or WindowGroup instances. Check not applicable.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented May 16, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a automation.claudeCodeNotificationHook setting (default true) that lets users suppress the Claude Code idle Notification hook independently of the main integration toggle. The shell script is split into base/notification/tail fragments assembled at runtime based on CMUX_CLAUDE_NOTIFICATION_HOOK_DISABLED, and the full settings pipeline (SwiftUI toggle, @AppStorage, JSONC config parsing, search index, navigation anchor, localisation) is wired consistently with existing Claude Code integration settings.

  • Shell hook assembly (Resources/bin/claude): The JSON is split into three string fragments and concatenated conditionally; both code paths produce well-formed JSON, and the check matches the existing CMUX_CURSOR_HOOKS_DISABLED / CMUX_GEMINI_HOOKS_DISABLED pattern exactly.
  • Swift settings layer (cmuxApp.swift, GhosttyTerminalView.swift, KeyboardShortcutSettingsFileStore.swift): The new notificationHookEnabled() accessor, @AppStorage binding, env-var injection, and JSONC parser path all follow the same shape as the existing hooksEnabled() / customClaudePath() patterns.
  • Localisation (Localizable.xcstrings): Non-en/ja main setting strings correctly carry "state": "new"; both the label and subtitle have been updated to remove implementation-detail copy ("Stop hook") that was flagged in earlier threads.

Confidence Score: 5/5

Safe to merge — the change is a clean additive toggle that follows the established pattern for all existing integration settings.

Every layer of the change (shell hook assembly, Swift accessor, AppStorage binding, JSONC parsing, search index, localisation) mirrors the existing claudeCodeHooksEnabled / cursorHooksEnabled / geminiHooksEnabled patterns without introducing new state owners, timing dependencies, or architectural divergence. The two JSON assembly paths both produce well-formed JSON. Previous thread concerns (provider-specific copy in the subtitle, locale state values) are resolved in this revision.

No files require special attention.

Important Files Changed

Filename Overview
Resources/bin/claude Splits HOOKS_JSON into base/notification/tail fragments; both assembly paths produce valid JSON and the conditional matches the existing disabled-flag pattern.
Sources/cmuxApp.swift Adds notificationHookEnabledKey/accessor, @AppStorage binding, UI toggle with correct .disabled/.opacity guard, and reset-defaults wiring — all consistent with existing integration settings patterns.
Sources/GhosttyTerminalView.swift Injects CMUX_CLAUDE_NOTIFICATION_HOOK_DISABLED=1 following the same unguarded pattern as CMUX_CURSOR_HOOKS_DISABLED and CMUX_GEMINI_HOOKS_DISABLED; harmless when main integration is off.
Sources/KeyboardShortcutSettingsFileStore.swift Adds claudeCodeNotificationHook JSONC parsing using jsonBool helper, consistent with adjacent integration bool settings.
Resources/Localizable.xcstrings Adds label, subtitle, and search-alias keys with en/ja translations; non-en/ja main strings correctly use state "new"; search alias strings intentionally use the same English config key + keyword text across all locales, matching the established pattern.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant SettingsUI as Settings UI (cmuxApp.swift)
    participant AppStorage as UserDefaults
    participant GTV as GhosttyTerminalView
    participant ShimScript as Resources/bin/claude
    participant ClaudeCode as Claude Code

    User->>SettingsUI: Toggle "Claude Code Idle Notifications" off
    SettingsUI->>AppStorage: "Write claudeCodeNotificationHookEnabled = false"

    User->>GTV: Open new terminal
    GTV->>AppStorage: Read notificationHookEnabled()
    AppStorage-->>GTV: false
    GTV->>GTV: setManagedEnvironmentValue(CMUX_CLAUDE_NOTIFICATION_HOOK_DISABLED, 1)

    User->>ClaudeCode: Run claude command
    ClaudeCode->>ShimScript: Execute shim
    ShimScript->>ShimScript: Check CMUX_CLAUDE_NOTIFICATION_HOOK_DISABLED
    alt Hook disabled
        ShimScript->>ShimScript: "HOOKS_JSON = HOOKS_BASE + HOOKS_TAIL"
    else Hook enabled
        ShimScript->>ShimScript: "HOOKS_JSON = HOOKS_BASE + HOOKS_NOTIFICATION + HOOKS_TAIL"
    end
    ShimScript->>ClaudeCode: exec claude --settings HOOKS_JSON
Loading

Reviews (2): Last reviewed commit: "Fix review feedback: remove internal hoo..." | Re-trigger Greptile

Comment thread Sources/cmuxApp.swift Outdated
SettingsCardRow(
configurationReview: .json("automation.claudeCodeNotificationHook"),
String(localized: "settings.automation.claudeCode.notificationHook", defaultValue: "Claude Code Idle Notifications"),
subtitle: String(localized: "settings.automation.claudeCode.notificationHook.subtitle", defaultValue: "Notify when Claude Code reports that it is waiting for input. Completion notifications still use the Stop hook.")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The subtitle exposes "Stop hook" — a Claude Code provider-specific hook name — directly to end users. The custom cmux rule requires that user-facing copy not include upstream vendor or provider-specific implementation terms. A user toggling this setting doesn't need to know the internal hook name; saying "Completion notifications are unaffected" conveys the same intent without leaking the implementation detail. The same wording appears verbatim as the defaultValue and will be shown any time the localized string is missing.

Suggested change
subtitle: String(localized: "settings.automation.claudeCode.notificationHook.subtitle", defaultValue: "Notify when Claude Code reports that it is waiting for input. Completion notifications still use the Stop hook.")
subtitle: String(localized: "settings.automation.claudeCode.notificationHook.subtitle", defaultValue: "Notify when Claude Code reports that it is waiting for input. Completion notifications are unaffected.")

Context Used: Apply cmux's custom review rules from .github/revi... (source)

Comment thread Resources/Localizable.xcstrings Outdated
Comment on lines +59711 to +59717
"value": "Notify when Claude Code reports that it is waiting for input. Completion notifications still use the Stop hook."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "Claude Codeが入力待ちのときに通知します。完了通知は引き続きStopフックを使用します。"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The English and Japanese subtitle values mention "Stop hook" / "Stopフック", leaking a Claude Code internal hook name into user-facing copy. Both should be updated to avoid the provider-specific term.

Suggested change
"value": "Notify when Claude Code reports that it is waiting for input. Completion notifications still use the Stop hook."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "Claude Codeが入力待ちのときに通知します。完了通知は引き続きStopフックを使用します。"
"value": "Notify when Claude Code reports that it is waiting for input. Completion notifications are unaffected."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "Claude Codeが入力待ちのときに通知します。完了通知には影響ありません。"

Context Used: Apply cmux's custom review rules from .github/revi... (source)

@@ -59583,6 +59583,244 @@
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Non-English locales marked as translated with untranslated text

Every locale other than en and ja for both settings.automation.claudeCode.notificationHook and its .subtitle key carries the English string but is flagged "state": "translated". Xcode's localization pipeline treats "translated" as production-ready and will surface these strings to users of those locales without any further warning. They should be "state": "needs_review" (or omitted entirely so the runtime falls back to the development language) until real translations are available. The same pattern is repeated in the search alias key settings.search.alias.setting.automation.claude-notification-hook.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 8 files

Re-trigger cubic

- Replace "Stop hook" with "are unaffected" in subtitle text
- Mark untranslated locales as "new" instead of "translated"
- Update Japanese subtitle accordingly
@Pawel-Kica

Copy link
Copy Markdown

Found a workaround that works right now on v0.64.13, no patch needed.

cmux already has a filter for this: notifications.hooks (https://cmux.com/docs/notifications). It runs before a notification shows and lets you kill the effects. It only sees the text, but that's enough to tell them apart:

  • the annoying one: body "Claude is waiting for your input"
  • permission: body "Claude needs your permission"
  • turn done: subtitle "Completed in <workspace>"

So you match on the idle body and drop just that. Drop this in ~/.config/cmux/cmux.json (you need jq):

{
  "notifications": {
    "hooks": [
      {
        "id": "mute-idle-waiting",
        "command": "jq -c 'if ((.notification.body // \"\") | test(\"waiting for your input\"; \"i\")) then .effects.desktop=false | .effects.sound=false | .effects.paneFlash=false | .effects.markUnread=false | .effects.reorderWorkspace=false else . end'"
      }
    ]
  }
}

Hot-reloads on save. The idle banner, sound, flash, unread ring, and reorder all go away, but permission prompts and completions still come through. And it fails open, so if the hook breaks you just get the banner back.

Not a real fix since it matches on text not notification_type, but it unblocks you today.

@teamleaderleo teamleaderleo added S3: minor Wrong behavior with a workaround area: agents Agent integrations (Claude Code, Codex, ACP), agent chat, hooks, status labels Sep 30, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: agents Agent integrations (Claude Code, Codex, ACP), agent chat, hooks, status S3: minor Wrong behavior with a workaround

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants