Repository navigation
Suppress nested agent hook notifications - #4334
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds nested prompt depth tracking and centralized session record helpers; implements process-ancestry suppression to avoid visible agent mutations for nested/subagent runs; introduces a Claude SubagentStop feed-only hook; applies suppression across Claude and Codex handlers; and adds tests, UI toggle, localization, schema, and environment wiring. ChangesNested Agent Visible Mutations Suppression with SubagentStop Hook
Estimated code review effort: Possibly related PRs:
Caution Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional.
❌ Failed checks (5 errors, 2 warnings)
✅ Passed checks (10 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift`:
- Around line 1058-1090: The test runCodexStopHookInFakeAgentTree creates a
symlink named "codex" pointing to /bin/sh (via FileManager.createSymbolicLink)
to simulate nested agent execution and thus depends on the production ancestry
detection using argv[0]/process name rather than resolved executable paths; add
a short comment above runCodexStopHookInFakeAgentTree documenting this
assumption (that the test relies on argv[0]/process name and will break if
ancestry detection is changed to resolve symlinks or inspect the real
executable) and mention why the symlink approach is used so future maintainers
understand the coupling.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 02a8f783-d8bd-4161-9952-7c9b027c7444
📒 Files selected for processing (5)
CLI/cmux.swiftResources/bin/claudecmuxTests/CLINotifyProcessIntegrationRegressionTests.swifttests/test_claude_wrapper_hooks.pytests/test_codex_feed_hooks.py
| private func runCodexStopHookInFakeAgentTree( | ||
| context: ClaudeHookContext, | ||
| nested: Bool, | ||
| standardInput: String | ||
| ) throws -> ProcessRunResult { | ||
| let fakeCodex = context.root.appendingPathComponent("codex", isDirectory: false) | ||
| try FileManager.default.createSymbolicLink(atPath: fakeCodex.path, withDestinationPath: "/bin/sh") | ||
|
|
||
| let hookCommand = "\(shellQuoteForTest(context.cliPath)) hooks codex stop; status=$?; exit $status" | ||
| let outerCommand: String | ||
| if nested { | ||
| outerCommand = "\(shellQuoteForTest(fakeCodex.path)) -c \(shellQuoteForTest(hookCommand)); status=$?; exit $status" | ||
| } else { | ||
| outerCommand = hookCommand | ||
| } | ||
|
|
||
| return runProcess( | ||
| executablePath: fakeCodex.path, | ||
| arguments: ["-c", outerCommand], | ||
| environment: [ | ||
| "HOME": context.root.path, | ||
| "PATH": "/usr/bin:/bin:/usr/sbin:/sbin", | ||
| "PWD": context.root.path, | ||
| "CMUX_SOCKET_PATH": context.socketPath, | ||
| "CMUX_WORKSPACE_ID": context.workspaceId, | ||
| "CMUX_SURFACE_ID": context.surfaceId, | ||
| "CMUX_AGENT_HOOK_STATE_DIR": context.root.path, | ||
| "CMUX_CLI_SENTRY_DISABLED": "1", | ||
| ], | ||
| standardInput: standardInput, | ||
| timeout: 5 | ||
| ) | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Symlink-based process tree simulation couples to ancestry detection implementation.
The test uses a symbolic link to /bin/sh named "codex" to simulate nested agent execution. This approach assumes the production ancestry detection logic inspects argv[0] or the process name rather than the resolved executable path. If the production code changes to resolve symlinks or check the actual executable, this test will fail.
Consider documenting this assumption in a comment above the function to help future maintainers understand the test's dependency on the ancestry detection mechanism.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift` around lines 1058
- 1090, The test runCodexStopHookInFakeAgentTree creates a symlink named "codex"
pointing to /bin/sh (via FileManager.createSymbolicLink) to simulate nested
agent execution and thus depends on the production ancestry detection using
argv[0]/process name rather than resolved executable paths; add a short comment
above runCodexStopHookInFakeAgentTree documenting this assumption (that the test
relies on argv[0]/process name and will break if ancestry detection is changed
to resolve symlinks or inspect the real executable) and mention why the symlink
approach is used so future maintainers understand the coupling.
Greptile SummaryThis PR suppresses visible hook mutations (notifications, status updates, PID bindings) when a Codex or Claude child agent fires hooks under a parent agent session, while keeping those events in Feed telemetry. It adds a default-on
Confidence Score: 4/5Safe to merge with awareness of the depth-counter edge case, which affects Codex sessions that share a session ID across runs and can be interrupted without a stop hook firing. The new activePromptDepth field is persisted in ClaudeHookSessionRecord and incremented by recordPromptSubmit, but only decremented by recordPromptStop. If the agent process is killed between submit and stop — a normal occurrence for Codex Teams threads — the counter stays at 1 in the store. The next recordPromptSubmit for the same session ID increments it to 2, returns true (nested), and shouldSuppressNestedAgentVisibleMutations short-circuits before the process-ancestry walk. The counter then oscillates 1-2-1-2 on every future cycle, permanently silencing all visible mutations for that session ID with no automatic recovery path. CLI/cmux.swift — specifically recordPromptSubmit, recordPromptStop, and shouldSuppressNestedAgentVisibleMutations: the interplay between the persisted depth counter and the suppression decision. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Hook fires: prompt-submit / stop] --> B{Suppression enabled?}
B -- No --> Z[Apply visible mutation]
B -- Yes --> C{Global override env var?}
C -- Yes --> SUP[Suppress]
C -- No --> D{nestedPromptEvent? depth > 1}
D -- Yes --> SUP
D -- No --> E{CMUX_AGENT_MANAGED_SUBAGENT=1?}
E -- Yes --> SUP
E -- No --> F{transcriptSubagentSession?}
F -- Yes --> SUP
F -- No --> G{Walk ancestor PIDs, count agents}
G -- 2 or more --> SUP
G -- fewer than 2 --> Z
Reviews (8): Last reviewed commit: "Address subagent suppression review feed..." | Re-trigger Greptile |
| private enum HookAgentProcessKind: String { | ||
| case codex | ||
| case claude | ||
| } |
There was a problem hiding this comment.
The
HookAgentProcessKind enum has two cases (.codex, .claude) but every call site in this PR only tests the result against nil — the actual kind is never consumed for differentiated logic. This makes the enum's RawRepresentable conformance and the two distinct cases dead state. Per the architectural-rethink rule, unused state should not be introduced. A plain Bool (or a helper that directly returns Bool) is sufficient for the current use, or the kind distinction should be used (e.g., to emit a kind-tagged breadcrumb, or to skip Codex-under-Codex differently from Codex-under-Claude).
| private enum HookAgentProcessKind: String { | |
| case codex | |
| case claude | |
| } | |
| private enum HookAgentProcessKind { | |
| case codex | |
| case claude | |
| } |
Rule Used: Flag Swift fixes that patch symptoms while leaving... (source)
| if nameBase == "node" || nameBase == "bun" || executableBase == "node" || executableBase == "bun" { | ||
| if arguments.dropFirst().contains(where: { argument in | ||
| let lowered = argument.lowercased() | ||
| return agentProcessBasename(argument) == "claude" | ||
| || lowered.contains("/.claude/") | ||
| || lowered.contains("/claude/versions/") | ||
| }) { | ||
| return .claude | ||
| } | ||
| return nil |
There was a problem hiding this comment.
node/bun branch returns nil for Claude launched without install-path markers
The current logic returns .claude for a node/bun process only when its arguments include /.claude/ or /claude/versions/. A node-launched Claude whose argv doesn't carry either path (e.g., a wrapper script at /usr/local/bin/claude that execs into node) would return nil and be invisible to the nesting counter, causing a false negative where a nested agent is treated as top-level. The risk is low with the current Claude Code install layout, but the heuristic is the only safeguard and is worth documenting.
There was a problem hiding this comment.
1 issue found across 16 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@CLI/cmux.swift`:
- Around line 19042-19184: The new hook/session suppression logic (functions
claudeAgentPID, shouldSuppressNestedAgentVisibleMutations,
subagentNotificationSuppressionEnabled, appDefaultsCandidates, parseHookBoolean,
nativeAgentProcessKind, nativeAgentProcessKind(processName:arguments:), and
agentProcessBasename) should be extracted from CLI/cmux.swift into a focused new
type/module (e.g., HookSuppressionManager or AgentProcessInspector in its own
Swift file or package) so the large CLI file is reduced by >200 lines; move the
implementations, expose only the needed API (e.g.,
shouldSuppressNestedAgentVisibleMutations(currentAgentPID:nestedPromptEvent:env:)),
update callers in cmux.swift to use the new type, adjust access levels/imports,
and ensure any references to processName(_:), processArguments(_:),
parentPID(_:), and suppressSubagentNotificationsEnvironmentKey remain available
(inject them or make them internal helpers) so behavior is unchanged.
- Around line 21411-21419: The block currently always calls store.consume(...)
and clears agent PID/status even for nested prompt stops; modify the logic in
the recordPromptStop flow to first check the nestedPromptStop flag and skip
calling store.consume(sessionId:..., workspaceId: nil, surfaceId: nil) (and the
subsequent sendAgentFeedTelemetry, shouldSuppressNestedAgentVisibleMutations,
and sendV1Command clear path) when nestedPromptStop is true so the outer session
state and visible PID/status are not torn down by an inner stop; keep the
existing behavior for non-nested stops so store.consume,
sendAgentFeedTelemetry(mapped.workspaceId),
shouldSuppressNestedAgentVisibleMutations(currentAgentPID: mapped.pid, env: env)
and the sendV1Command("clear_agent_pid \(pidKey)
--tab=\(mapped.workspaceId)\(socketPanelOption(mapped.surfaceId))
--clear-status", client: client) call only run when nestedPromptStop is false.
In `@Resources/Localizable.xcstrings`:
- Around line 59944-60035: The new localization keys
settings.automation.suppressSubagentNotifications,
settings.automation.suppressSubagentNotifications.note,
settings.automation.suppressSubagentNotifications.subtitleOff, and
settings.automation.suppressSubagentNotifications.subtitleOn are missing
supported locales; add entries for ar, bs, da, pl, th, km, and tr to each key
using the English "en" value as the fallback text and set each added
stringUnit.state to "translated" (i.e., mirror the en.value for each missing
locale and mark state "translated") so every supported locale is present for
these keys.
In `@Sources/SettingsSearchAliases.swift`:
- Line 91: The new localization key
"settings.search.alias.setting.automation.subagent-notifications" in
SettingsSearchAliases.swift currently has translations only for en/ja/ko; add
translated entries for the remaining 16 supported locales (ar, bs, da, de, es,
fr, it, nb, pl, pt-BR, ru, th, tr, uk, zh-Hans, zh-Hant) in the app string
catalog so every locale that the catalog supports includes this key; update each
locale's strings file (or localization resource) with an accurate translation
for that key, using the provided defaultValue as a fallback if a translator is
unavailable, and ensure the key string and identical identifier
"settings.search.alias.setting.automation.subagent-notifications" are added to
each locale block so builds and runtime lookups succeed.
In `@web/data/cmux.schema.json`:
- Around line 658-662: The new schema property suppressSubagentNotifications in
web/data/cmux.schema.json adds English-only description text; update
localization by adding a matching localized message key (e.g.,
"cmux.suppressSubagentNotifications.description") to each locale referenced in
web/i18n/routing.ts and replace or wire the schema description to use that
localized message ID so all locales have coverage; ensure message files (e.g.,
web/i18n/<locale>/messages.json) include the translated string for every locale
and that any schema-to-i18n lookup code uses the same key.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 0fb24329-0c1b-412a-94d5-ca4b787b6872
📒 Files selected for processing (16)
CLI/cmux.swiftResources/Localizable.xcstringsSources/CmuxSettingsJSONPathSupport.swiftSources/CommandPalette/CommandPaletteSettingsToggle.swiftSources/GhosttyTerminalView.swiftSources/KeyboardShortcutSettingsFileStore+Template.swiftSources/KeyboardShortcutSettingsFileStore.swiftSources/SettingsNavigation.swiftSources/SettingsSearchAliases.swiftSources/cmuxApp.swiftcmuxTests/CLINotifyProcessIntegrationRegressionTests.swiftcmuxTests/CommandPaletteSettingsToggleTests.swiftcmuxTests/GhosttyConfigTests.swiftcmuxTests/WorkspaceUnitTests.swiftweb/app/[locale]/docs/configuration/page.tsxweb/data/cmux.schema.json
| private func claudeAgentPID(from env: [String: String]) -> Int? { | ||
| guard let raw = env["CMUX_CLAUDE_PID"]? | ||
| .trimmingCharacters(in: .whitespacesAndNewlines), | ||
| let pid = Int(raw), | ||
| pid > 0 else { | ||
| return nil | ||
| } | ||
| return pid | ||
| } | ||
|
|
||
| private func shouldSuppressNestedAgentVisibleMutations( | ||
| currentAgentPID: Int?, | ||
| nestedPromptEvent: Bool = false, | ||
| env: [String: String] | ||
| ) -> Bool { | ||
| if let override = normalizedHookValue(env["CMUX_AGENT_HOOK_SUPPRESS_VISIBLE_MUTATIONS"])?.lowercased(), | ||
| ["1", "true", "yes"].contains(override) { | ||
| return true | ||
| } | ||
|
|
||
| guard subagentNotificationSuppressionEnabled(env: env) else { | ||
| return false | ||
| } | ||
|
|
||
| if nestedPromptEvent { | ||
| return true | ||
| } | ||
|
|
||
| guard let currentAgentPID, currentAgentPID > 1 else { | ||
| return false | ||
| } | ||
|
|
||
| var candidate = pid_t(currentAgentPID) | ||
| var agentProcessCount = 0 | ||
| var remainingAncestors = 32 | ||
| while candidate > 1, remainingAncestors > 0 { | ||
| if nativeAgentProcessKind(for: candidate) != nil { | ||
| agentProcessCount += 1 | ||
| if agentProcessCount >= 2 { | ||
| return true | ||
| } | ||
| } | ||
| let next = parentPID(of: candidate) | ||
| guard next > 1, next != candidate else { | ||
| break | ||
| } | ||
| candidate = next | ||
| remainingAncestors -= 1 | ||
| } | ||
| return false | ||
| } | ||
|
|
||
| private func subagentNotificationSuppressionEnabled(env: [String: String]) -> Bool { | ||
| if let raw = normalizedHookValue(env[suppressSubagentNotificationsEnvironmentKey]), | ||
| let parsed = Self.parseHookBoolean(raw) { | ||
| return parsed | ||
| } | ||
| for defaults in appDefaultsCandidates(env: env) { | ||
| if defaults.object(forKey: suppressSubagentNotificationsDefaultsKey) != nil { | ||
| return defaults.bool(forKey: suppressSubagentNotificationsDefaultsKey) | ||
| } | ||
| } | ||
| return true | ||
| } | ||
|
|
||
| private func appDefaultsCandidates(env: [String: String]) -> [UserDefaults] { | ||
| var candidates: [UserDefaults] = [] | ||
| if let bundleId = normalizedHookValue(env["CMUX_BUNDLE_ID"]), | ||
| let defaults = UserDefaults(suiteName: bundleId) { | ||
| candidates.append(defaults) | ||
| } | ||
| candidates.append(.standard) | ||
| return candidates | ||
| } | ||
|
|
||
| private static func parseHookBoolean(_ rawValue: String) -> Bool? { | ||
| switch rawValue.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() { | ||
| case "1", "true", "yes", "on", "enabled": | ||
| return true | ||
| case "0", "false", "no", "off", "disabled": | ||
| return false | ||
| default: | ||
| return nil | ||
| } | ||
| } | ||
|
|
||
| private func nativeAgentProcessKind(for pid: pid_t) -> HookAgentProcessKind? { | ||
| let name = processName(for: pid) | ||
| if let kind = Self.nativeAgentProcessKind(processName: name, arguments: []) { | ||
| return kind | ||
| } | ||
|
|
||
| let nameBase = Self.agentProcessBasename(name) | ||
| guard nameBase == "node" || nameBase == "bun" else { | ||
| return nil | ||
| } | ||
|
|
||
| return Self.nativeAgentProcessKind( | ||
| processName: name, | ||
| arguments: processArguments(for: pid) ?? [] | ||
| ) | ||
| } | ||
|
|
||
| private static func nativeAgentProcessKind( | ||
| processName: String?, | ||
| arguments: [String] | ||
| ) -> HookAgentProcessKind? { | ||
| let nameBase = agentProcessBasename(processName) | ||
| let executableBase = agentProcessBasename(arguments.first) | ||
|
|
||
| // Codex's npm/bun launcher leaves a node process above the native | ||
| // Codex binary. That wrapper is part of the same launch, not a | ||
| // parent agent, so only native Codex executables count. Claude Code | ||
| // can run as a node script, so keep that as an agent process. | ||
| if nameBase == "node" || nameBase == "bun" || executableBase == "node" || executableBase == "bun" { | ||
| if arguments.dropFirst().contains(where: { argument in | ||
| let lowered = argument.lowercased() | ||
| return agentProcessBasename(argument) == "claude" | ||
| || lowered.contains("/.claude/") | ||
| || lowered.contains("/claude/versions/") | ||
| }) { | ||
| return .claude | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| let executable = arguments.first?.lowercased() ?? "" | ||
| if nameBase == "codex" || executableBase == "codex" || executable.contains("/codex/codex") { | ||
| return .codex | ||
| } | ||
| if nameBase == "claude" || executableBase == "claude" || executable.contains("/claude/versions/") { | ||
| return .claude | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| private static func agentProcessBasename(_ value: String?) -> String? { | ||
| guard let value = value?.trimmingCharacters(in: .whitespacesAndNewlines), | ||
| !value.isEmpty else { | ||
| return nil | ||
| } | ||
| return URL(fileURLWithPath: value).lastPathComponent.lowercased() | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift
Extract this new hook/session suppression logic out of CLI/cmux.swift.
This file is already far beyond the 800-line threshold, and this diff adds well over 250 new lines of state-management and process-ancestry code here. Please move this block into a focused type/module before merge.
As per coding guidelines, **/{Sources,CLI,Packages,cmuxTests,cmuxUITests}/**/*.swift: "Do not add more than 250 lines to an existing production Swift file that is already over 800 lines, unless an extraction exception is met by removing mixed responsibilities or moving code into a package that shrinks the file by more than 200 lines."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@CLI/cmux.swift` around lines 19042 - 19184, The new hook/session suppression
logic (functions claudeAgentPID, shouldSuppressNestedAgentVisibleMutations,
subagentNotificationSuppressionEnabled, appDefaultsCandidates, parseHookBoolean,
nativeAgentProcessKind, nativeAgentProcessKind(processName:arguments:), and
agentProcessBasename) should be extracted from CLI/cmux.swift into a focused new
type/module (e.g., HookSuppressionManager or AgentProcessInspector in its own
Swift file or package) so the large CLI file is reduced by >200 lines; move the
implementations, expose only the needed API (e.g.,
shouldSuppressNestedAgentVisibleMutations(currentAgentPID:nestedPromptEvent:env:)),
update callers in cmux.swift to use the new type, adjust access levels/imports,
and ensure any references to processName(_:), processArguments(_:),
parentPID(_:), and suppressSubagentNotificationsEnvironmentKey remain available
(inject them or make them internal helpers) so behavior is unchanged.
| "settings.automation.suppressSubagentNotifications": { | ||
| "extractionState": "manual", | ||
| "localizations": { | ||
| "en": { | ||
| "stringUnit": { | ||
| "state": "translated", | ||
| "value": "Suppress Subagent Notifications" | ||
| } | ||
| }, | ||
| "ja": { | ||
| "stringUnit": { | ||
| "state": "translated", | ||
| "value": "サブエージェント通知を抑制" | ||
| } | ||
| }, | ||
| "ko": { | ||
| "stringUnit": { | ||
| "state": "translated", | ||
| "value": "하위 에이전트 알림 억제" | ||
| } | ||
| } | ||
| } | ||
| }, | ||
| "settings.automation.suppressSubagentNotifications.note": { | ||
| "extractionState": "manual", | ||
| "localizations": { | ||
| "en": { | ||
| "stringUnit": { | ||
| "state": "translated", | ||
| "value": "Uses process ancestry from hook processes. Disable if nested Codex or Claude sessions should trigger completion notifications." | ||
| } | ||
| }, | ||
| "ja": { | ||
| "stringUnit": { | ||
| "state": "translated", | ||
| "value": "フックプロセスの親子関係を使用します。ネストされたCodexまたはClaudeセッションでも完了通知を出したい場合は無効にしてください。" | ||
| } | ||
| }, | ||
| "ko": { | ||
| "stringUnit": { | ||
| "state": "translated", | ||
| "value": "훅 프로세스의 상위 프로세스 관계를 사용합니다. 중첩된 Codex 또는 Claude 세션도 완료 알림을 보내야 한다면 비활성화하세요." | ||
| } | ||
| } | ||
| } | ||
| }, | ||
| "settings.automation.suppressSubagentNotifications.subtitleOff": { | ||
| "extractionState": "manual", | ||
| "localizations": { | ||
| "en": { | ||
| "stringUnit": { | ||
| "state": "translated", | ||
| "value": "Child agent completions notify like top-level agents." | ||
| } | ||
| }, | ||
| "ja": { | ||
| "stringUnit": { | ||
| "state": "translated", | ||
| "value": "子エージェントの完了もトップレベルのエージェントと同様に通知します。" | ||
| } | ||
| }, | ||
| "ko": { | ||
| "stringUnit": { | ||
| "state": "translated", | ||
| "value": "하위 에이전트 완료도 최상위 에이전트처럼 알립니다." | ||
| } | ||
| } | ||
| } | ||
| }, | ||
| "settings.automation.suppressSubagentNotifications.subtitleOn": { | ||
| "extractionState": "manual", | ||
| "localizations": { | ||
| "en": { | ||
| "stringUnit": { | ||
| "state": "translated", | ||
| "value": "Child agent completions stay in Feed without notifications." | ||
| } | ||
| }, | ||
| "ja": { | ||
| "stringUnit": { | ||
| "state": "translated", | ||
| "value": "子エージェントの完了は通知せずFeedにのみ残ります。" | ||
| } | ||
| }, | ||
| "ko": { | ||
| "stringUnit": { | ||
| "state": "translated", | ||
| "value": "하위 에이전트 완료는 알림 없이 Feed에만 남습니다." | ||
| } | ||
| } | ||
| } | ||
| }, |
There was a problem hiding this comment.
Missing locale entries for all supported locales.
All four new settings.automation.suppressSubagentNotifications.* keys include only en, ja, and ko localizations. According to the retrieved learning and coding guidelines, this catalog supports at least ar, bs, da, pl, th, km, and tr as well. The coding guidelines explicitly require "every supported locale in the touched catalog" and directive to "fail partial localization."
Each new key must include entries for all supported locales. For lower-confidence locales (ar, bs, da, pl, th, km, tr), use English-language fallback values with state: "translated" per the established .xcstrings convention. As per coding guidelines, app string catalogs must include every supported locale for new entries, not just a subset.
🌐 Example fix for one key with missing locales
For settings.automation.suppressSubagentNotifications, add entries like:
"ko": {
"stringUnit": {
"state": "translated",
"value": "하위 에이전트 알림 억제"
}
- }
+ },
+ "ar": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "Suppress Subagent Notifications"
+ }
+ },
+ "bs": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "Suppress Subagent Notifications"
+ }
+ },
+ "da": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "Suppress Subagent Notifications"
+ }
+ },
+ "pl": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "Suppress Subagent Notifications"
+ }
+ },
+ "th": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "Suppress Subagent Notifications"
+ }
+ },
+ "km": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "Suppress Subagent Notifications"
+ }
+ },
+ "tr": {
+ "stringUnit": {
+ "state": "translated",
+ "value": "Suppress Subagent Notifications"
+ }
+ }
}
},Apply the same pattern to .note, .subtitleOff, .subtitleOn, and the search alias key, using the appropriate English text for each.
Based on learnings, the catalog follows the convention of using English fallback with state: "translated" for lower-confidence locales (ar, bs, da, pl, th, km, tr) on newly added keys.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "settings.automation.suppressSubagentNotifications": { | |
| "extractionState": "manual", | |
| "localizations": { | |
| "en": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Suppress Subagent Notifications" | |
| } | |
| }, | |
| "ja": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "サブエージェント通知を抑制" | |
| } | |
| }, | |
| "ko": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "하위 에이전트 알림 억제" | |
| } | |
| } | |
| } | |
| }, | |
| "settings.automation.suppressSubagentNotifications.note": { | |
| "extractionState": "manual", | |
| "localizations": { | |
| "en": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Uses process ancestry from hook processes. Disable if nested Codex or Claude sessions should trigger completion notifications." | |
| } | |
| }, | |
| "ja": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "フックプロセスの親子関係を使用します。ネストされたCodexまたはClaudeセッションでも完了通知を出したい場合は無効にしてください。" | |
| } | |
| }, | |
| "ko": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "훅 프로세스의 상위 프로세스 관계를 사용합니다. 중첩된 Codex 또는 Claude 세션도 완료 알림을 보내야 한다면 비활성화하세요." | |
| } | |
| } | |
| } | |
| }, | |
| "settings.automation.suppressSubagentNotifications.subtitleOff": { | |
| "extractionState": "manual", | |
| "localizations": { | |
| "en": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Child agent completions notify like top-level agents." | |
| } | |
| }, | |
| "ja": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "子エージェントの完了もトップレベルのエージェントと同様に通知します。" | |
| } | |
| }, | |
| "ko": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "하위 에이전트 완료도 최상위 에이전트처럼 알립니다." | |
| } | |
| } | |
| } | |
| }, | |
| "settings.automation.suppressSubagentNotifications.subtitleOn": { | |
| "extractionState": "manual", | |
| "localizations": { | |
| "en": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Child agent completions stay in Feed without notifications." | |
| } | |
| }, | |
| "ja": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "子エージェントの完了は通知せずFeedにのみ残ります。" | |
| } | |
| }, | |
| "ko": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "하위 에이전트 완료는 알림 없이 Feed에만 남습니다." | |
| } | |
| } | |
| } | |
| }, | |
| "settings.automation.suppressSubagentNotifications": { | |
| "extractionState": "manual", | |
| "localizations": { | |
| "en": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Suppress Subagent Notifications" | |
| } | |
| }, | |
| "ja": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "サブエージェント通知を抑制" | |
| } | |
| }, | |
| "ko": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "하위 에이전트 알림 억제" | |
| } | |
| }, | |
| "ar": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Suppress Subagent Notifications" | |
| } | |
| }, | |
| "bs": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Suppress Subagent Notifications" | |
| } | |
| }, | |
| "da": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Suppress Subagent Notifications" | |
| } | |
| }, | |
| "pl": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Suppress Subagent Notifications" | |
| } | |
| }, | |
| "th": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Suppress Subagent Notifications" | |
| } | |
| }, | |
| "km": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Suppress Subagent Notifications" | |
| } | |
| }, | |
| "tr": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Suppress Subagent Notifications" | |
| } | |
| } | |
| } | |
| }, | |
| "settings.automation.suppressSubagentNotifications.note": { | |
| "extractionState": "manual", | |
| "localizations": { | |
| "en": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Uses process ancestry from hook processes. Disable if nested Codex or Claude sessions should trigger completion notifications." | |
| } | |
| }, | |
| "ja": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "フックプロセスの親子関係を使用します。ネストされたCodexまたはClaudeセッションでも完了通知を出したい場合は無効にしてください。" | |
| } | |
| }, | |
| "ko": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "훅 프로세스의 상위 프로세스 관계를 사용합니다. 중첩된 Codex 또는 Claude 세션도 완료 알림을 보내야 한다면 비활성화하세요." | |
| } | |
| }, | |
| "ar": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Uses process ancestry from hook processes. Disable if nested Codex or Claude sessions should trigger completion notifications." | |
| } | |
| }, | |
| "bs": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Uses process ancestry from hook processes. Disable if nested Codex or Claude sessions should trigger completion notifications." | |
| } | |
| }, | |
| "da": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Uses process ancestry from hook processes. Disable if nested Codex or Claude sessions should trigger completion notifications." | |
| } | |
| }, | |
| "pl": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Uses process ancestry from hook processes. Disable if nested Codex or Claude sessions should trigger completion notifications." | |
| } | |
| }, | |
| "th": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Uses process ancestry from hook processes. Disable if nested Codex or Claude sessions should trigger completion notifications." | |
| } | |
| }, | |
| "km": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Uses process ancestry from hook processes. Disable if nested Codex or Claude sessions should trigger completion notifications." | |
| } | |
| }, | |
| "tr": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Uses process ancestry from hook processes. Disable if nested Codex or Claude sessions should trigger completion notifications." | |
| } | |
| } | |
| } | |
| }, | |
| "settings.automation.suppressSubagentNotifications.subtitleOff": { | |
| "extractionState": "manual", | |
| "localizations": { | |
| "en": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Child agent completions notify like top-level agents." | |
| } | |
| }, | |
| "ja": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "子エージェントの完了もトップレベルのエージェントと同様に通知します。" | |
| } | |
| }, | |
| "ko": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "하위 에이전트 완료도 최상위 에이전트처럼 알립니다." | |
| } | |
| }, | |
| "ar": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Child agent completions notify like top-level agents." | |
| } | |
| }, | |
| "bs": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Child agent completions notify like top-level agents." | |
| } | |
| }, | |
| "da": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Child agent completions notify like top-level agents." | |
| } | |
| }, | |
| "pl": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Child agent completions notify like top-level agents." | |
| } | |
| }, | |
| "th": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Child agent completions notify like top-level agents." | |
| } | |
| }, | |
| "km": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Child agent completions notify like top-level agents." | |
| } | |
| }, | |
| "tr": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Child agent completions notify like top-level agents." | |
| } | |
| } | |
| } | |
| }, | |
| "settings.automation.suppressSubagentNotifications.subtitleOn": { | |
| "extractionState": "manual", | |
| "localizations": { | |
| "en": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Child agent completions stay in Feed without notifications." | |
| } | |
| }, | |
| "ja": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "子エージェントの完了は通知せずFeedにのみ残ります。" | |
| } | |
| }, | |
| "ko": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "하위 에이전트 완료는 알림 없이 Feed에만 남습니다." | |
| } | |
| }, | |
| "ar": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Child agent completions stay in Feed without notifications." | |
| } | |
| }, | |
| "bs": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Child agent completions stay in Feed without notifications." | |
| } | |
| }, | |
| "da": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Child agent completions stay in Feed without notifications." | |
| } | |
| }, | |
| "pl": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Child agent completions stay in Feed without notifications." | |
| } | |
| }, | |
| "th": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Child agent completions stay in Feed without notifications." | |
| } | |
| }, | |
| "km": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Child agent completions stay in Feed without notifications." | |
| } | |
| }, | |
| "tr": { | |
| "stringUnit": { | |
| "state": "translated", | |
| "value": "Child agent completions stay in Feed without notifications." | |
| } | |
| } | |
| } | |
| }, |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Resources/Localizable.xcstrings` around lines 59944 - 60035, The new
localization keys settings.automation.suppressSubagentNotifications,
settings.automation.suppressSubagentNotifications.note,
settings.automation.suppressSubagentNotifications.subtitleOff, and
settings.automation.suppressSubagentNotifications.subtitleOn are missing
supported locales; add entries for ar, bs, da, pl, th, km, and tr to each key
using the English "en" value as the fallback text and set each added
stringUnit.state to "translated" (i.e., mirror the en.value for each missing
locale and mark state "translated") so every supported locale is present for
these keys.
| "suppressSubagentNotifications": { | ||
| "type": "boolean", | ||
| "default": true, | ||
| "description": "Suppress visible completion notifications and status mutations from nested Codex or Claude child agents while keeping their events in Feed telemetry." | ||
| }, |
There was a problem hiding this comment.
Localize new schema description text across supported locales.
This adds new user-facing schema copy in English only. The docs/UI consume schema descriptions, so this introduces partial localization for the new setting.
As per coding guidelines, “web/data schema titles, descriptions, and data copy must have matching localized message coverage for every locale listed in web/i18n/routing.ts.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/data/cmux.schema.json` around lines 658 - 662, The new schema property
suppressSubagentNotifications in web/data/cmux.schema.json adds English-only
description text; update localization by adding a matching localized message key
(e.g., "cmux.suppressSubagentNotifications.description") to each locale
referenced in web/i18n/routing.ts and replace or wire the schema description to
use that localized message ID so all locales have coverage; ensure message files
(e.g., web/i18n/<locale>/messages.json) include the translated string for every
locale and that any schema-to-i18n lookup code uses the same key.
There was a problem hiding this comment.
♻️ Duplicate comments (1)
CLI/cmux.swift (1)
21575-21583:⚠️ Potential issue | 🟠 Major | ⚡ Quick winCarry the earlier suppression decision into the final consume/clear path.
This block recomputes suppression from only
mapped.pid+env, so a stop that was suppressed because ofnestedPromptStoporcodexSubagentSignals.isSubagentSessioncan still consume the session and clear the visible PID/status here. That reintroduces the exact teardown leak this change is trying to prevent.Suggested fix
- if let mapped = try? store.consume(sessionId: sessionId, workspaceId: nil, surfaceId: nil) { + if !nestedPromptStop, + let mapped = try? store.consume(sessionId: sessionId, workspaceId: nil, surfaceId: nil) { sendAgentFeedTelemetry(workspaceId: mapped.workspaceId) - let suppressVisibleMutations = shouldSuppressNestedAgentVisibleMutations(currentAgentPID: mapped.pid, env: env) + let suppressVisibleMutations = shouldSuppressNestedAgentVisibleMutations( + currentAgentPID: mapped.pid, + transcriptSubagentSession: codexSubagentSignals.isSubagentSession, + env: env + ) if !suppressVisibleMutations { _ = try? sendV1Command( "clear_agent_pid \(pidKey) --tab=\(mapped.workspaceId)\(socketPanelOption(mapped.surfaceId)) --clear-status",🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@CLI/cmux.swift` around lines 21575 - 21583, The code recomputes suppression using shouldSuppressNestedAgentVisibleMutations(mapped.pid, env) right before clearing, which can undo an earlier suppression decision (e.g., due to nestedPromptStop or codexSubagentSignals.isSubagentSession) and leak teardown; instead propagate the original suppression decision into this consume/clear path — either capture the earlier computed flag (e.g., suppressVisibleMutations or wasSuppressed) and use that here, or include a suppression flag in the value returned by store.consume (mapped.suppressVisibleMutations) and check that flag before calling sendV1Command("clear_agent_pid ...") so you never recompute from mapped.pid alone.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@CLI/cmux.swift`:
- Around line 21575-21583: The code recomputes suppression using
shouldSuppressNestedAgentVisibleMutations(mapped.pid, env) right before
clearing, which can undo an earlier suppression decision (e.g., due to
nestedPromptStop or codexSubagentSignals.isSubagentSession) and leak teardown;
instead propagate the original suppression decision into this consume/clear path
— either capture the earlier computed flag (e.g., suppressVisibleMutations or
wasSuppressed) and use that here, or include a suppression flag in the value
returned by store.consume (mapped.suppressVisibleMutations) and check that flag
before calling sendV1Command("clear_agent_pid ...") so you never recompute from
mapped.pid alone.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: c3efd386-e380-4344-b28d-562a8683b01c
📒 Files selected for processing (2)
CLI/cmux.swiftcmuxTests/CLINotifyProcessIntegrationRegressionTests.swift
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 0119b6f. Configure here.
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
…ation-suppression # Conflicts: # CLI/cmux.swift # Resources/Localizable.xcstrings # Sources/CmuxSettingsJSONPathSupport.swift # Sources/GhosttyTerminalView.swift # Sources/KeyboardShortcutSettingsFileStore+Template.swift # Sources/KeyboardShortcutSettingsFileStore.swift # Sources/SettingsNavigation.swift # Sources/SettingsSearchAliases.swift # Sources/cmuxApp.swift # cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift # web/data/cmux.schema.json
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
Sources/GhosttyTerminalView.swift (1)
3943-3954:⚠️ Potential issue | 🟠 Major | ⚡ Quick winScope notification suppression to the emitting panel.
This helper only sees workspace-wide
agentPIDs, so once any Codex/Claude panel is active it suppresses raw desktop notifications for every other panel in that workspace too. Both call sites passworkspace.agentPIDs, not the triggering panel's agent state. Please make this decision panel/surface-scoped instead of workspace-scoped.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Sources/GhosttyTerminalView.swift` around lines 3943 - 3954, The helper should make suppression decisions based on the emitting panel's agent state instead of the workspace-wide map; change the static function shouldSuppressAgentManagedDesktopNotification in GhosttyTerminalView to take the emitting panel's agent PID map (e.g., a new parameter like emittingAgentPIDs: [String: pid_t]) and use that map for the Claude/Codex key checks (instead of agentPIDs passed today), and update all call sites that currently pass workspace.agentPIDs to pass the specific panel's agentPIDs (or default to the panel-scoped map) so suppression is scoped to the emitting panel only; keep existing claudeHooksEnabled and suppressSubagentNotifications parameters unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@Sources/GhosttyTerminalView.swift`:
- Around line 3943-3954: The helper should make suppression decisions based on
the emitting panel's agent state instead of the workspace-wide map; change the
static function shouldSuppressAgentManagedDesktopNotification in
GhosttyTerminalView to take the emitting panel's agent PID map (e.g., a new
parameter like emittingAgentPIDs: [String: pid_t]) and use that map for the
Claude/Codex key checks (instead of agentPIDs passed today), and update all call
sites that currently pass workspace.agentPIDs to pass the specific panel's
agentPIDs (or default to the panel-scoped map) so suppression is scoped to the
emitting panel only; keep existing claudeHooksEnabled and
suppressSubagentNotifications parameters unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 4d29b4ee-d167-48b8-acc1-4a98184ce28a
📒 Files selected for processing (15)
CLI/cmux.swiftResources/Localizable.xcstringsSources/CmuxSettingsJSONPathSupport.swiftSources/CommandPalette/CommandPaletteSettingsToggle.swiftSources/GhosttyTerminalView.swiftSources/KeyboardShortcutSettingsFileStore+Template.swiftSources/KeyboardShortcutSettingsFileStore.swiftSources/SettingsNavigation.swiftSources/SettingsSearchAliases.swiftSources/cmuxApp.swiftcmuxTests/CLINotifyProcessIntegrationRegressionTests.swiftcmuxTests/GhosttyConfigTests.swiftcmuxTests/WorkspaceUnitTests.swifttests/test_codex_feed_hooks.pyweb/data/cmux.schema.json
💤 Files with no reviewable changes (8)
- Resources/Localizable.xcstrings
- web/data/cmux.schema.json
- cmuxTests/WorkspaceUnitTests.swift
- cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift
- Sources/cmuxApp.swift
- cmuxTests/GhosttyConfigTests.swift
- tests/test_codex_feed_hooks.py
- CLI/cmux.swift
There was a problem hiding this comment.
Actionable comments posted: 17
♻️ Duplicate comments (1)
Resources/Localizable.xcstrings (1)
60508-60508:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
kmlocale is still missing for all newly added keys.These five new entries still omit Khmer (
km), so locale coverage remains incomplete for this catalog update.Based on learnings, lower-confidence locales currently include
km, and it should be present (English fallback is acceptable there when marked translated).Also applies to: 60512-60512, 60516-60516, 60520-60520, 78587-78587
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Resources/Localizable.xcstrings` at line 60508, Add the missing Khmer (km) locale entries for the newly added localization keys that currently have "Suppress Subagent Notifications" (and the four other new keys noted at the same diff ranges) in Resources/Localizable.xcstrings by inserting a "km":{"stringUnit":{"state":"translated","value":"Suppress Subagent Notifications"}} entry alongside the existing locales; apply the same treatment (English fallback marked translated) for the other affected diffs referenced (60512, 60516, 60520, 78587) so each new key includes a km locale.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Resources/Localizable.xcstrings`:
- Line 60508: Several non-English locales for the string "Suppress Subagent
Notifications" incorrectly have English copy with state:"translated"; locate the
entries for that string (locales: de, es, fr, it, nb, pt-BR, ru, uk, zh-Hans,
zh-Hant and the other affected offsets) and remove the English value and/or
clear the localized "value" and change "state" from "translated" to the
repo-standard untranslated state (e.g., "needs-translation" or "untranslated")
so these slots are not satisfied by copied English; ensure only real localized
translations keep state:"translated" and run the localization checks again.
In `@Sources/GhosttyTerminalView.swift`:
- Around line 3978-3980: The condition currently uses
workspace.agentPIDs(forPanelId: surfaceId) and
Self.shouldSuppressAgentManagedDesktopNotification(agentPIDs:) which relies on
visible agent PID ownership; instead, read the nested-session "suppress-visible"
signal used by the hook pipeline and drive suppression from that state. Replace
uses of workspace.agentPIDs(forPanelId:) (and the call sites of
shouldSuppressAgentManagedDesktopNotification at the surfaceId checks around the
block and the similar spots at the other occurrence) with the nested-session
suppression flag/property (the same value the hook pipeline sets for nested
runs) so only nested/subagent runs suppress desktop notifications, not top-level
agent notifications.
In `@web/messages/da.json`:
- Around line 304-308: The Danish locale file web/messages/da.json contains an
English string for schemaDescriptions.automation.suppressSubagentNotifications;
replace the English copy with a proper Danish translation for the value of the
key "schemaDescriptions.automation.suppressSubagentNotifications" so the
high-confidence locale is fully localized, and ensure the key exactly matches
other locales' keys and punctuation/spacing.
In `@web/messages/de.json`:
- Around line 304-307: Update the German locale entry for
schemaDescriptions.automation.suppressSubagentNotifications so it is fully
translated: replace the English value with a concise German translation such as
"Unterdrücke sichtbare Abschlussbenachrichtigungen und Statusänderungen von
verschachtelten Codex‑ oder Claude‑Child‑Agenten, behalte deren Ereignisse
jedoch in der Feed‑Telemetrie bei." Ensure you modify the value for
schemaDescriptions.automation.suppressSubagentNotifications in the de locale.
In `@web/messages/es.json`:
- Around line 304-307: Translate the English string under
schemaDescriptions.automation.suppressSubagentNotifications into Spanish and
replace the current English value with that translation so the key is fully
localized; update the value for "suppressSubagentNotifications" (inside
"schemaDescriptions" -> "automation") with an accurate Spanish sentence
conveying: "Suppress visible completion notifications and status mutations from
nested Codex or Claude child agents while keeping their events in Feed
telemetry."
In `@web/messages/fr.json`:
- Around line 304-307: The JSON entry
schemaDescriptions.automation.suppressSubagentNotifications is still in English;
replace its value with a proper French translation so the high-confidence locale
file is fully localized (e.g., translate "Suppress visible completion
notifications and status mutations from nested Codex or Claude child agents
while keeping their events in Feed telemetry." into French) and ensure the key
name remains unchanged.
In `@web/messages/it.json`:
- Line 306: Translate the English value for the key
"suppressSubagentNotifications" into Italian and replace the current English
string in the it.json locale entry so the new schema description is fully
localized; ensure the translated text preserves meaning about suppressing
visible completion notifications and status mutations from nested Codex/Claude
child agents while keeping their events in Feed telemetry and matches
punctuation/style used across other Italian messages.
In `@web/messages/ja.json`:
- Around line 362-365: Add a Japanese translation for the new string at
schemaDescriptions.automation.suppressSubagentNotifications in
web/messages/ja.json: replace or augment the English text with a natural
Japanese equivalent that preserves meaning (e.g., suppress visible completion
notifications and status mutations from nested Codex or Claude child agents
while keeping their events in Feed telemetry → 「ネストされた Codex や Claude
の子エージェントからの表示される完了通知やステータス変更を抑制し、イベントはフィードのテレメトリには残す」) and ensure it follows
existing JSON quoting/escaping conventions and matches surrounding localization
style.
In `@web/messages/ko.json`:
- Around line 304-307: The JSON key
schemaDescriptions.automation.suppressSubagentNotifications is still English;
replace its value with a fluent Korean translation that preserves meaning and
any punctuation or placeholders (i.e., keep the key name exactly the same), for
example describing suppression of visible completion notifications and status
changes from nested Codex/Claude child agents while still recording their events
in Feed telemetry; update the value in the ko localization to a natural Korean
sentence matching existing tone and locale conventions.
In `@web/messages/no.json`:
- Around line 304-308: The entry
schemaDescriptions.automation.suppressSubagentNotifications currently contains
the English string for the Norwegian locale; replace it with a proper Norwegian
translation (e.g. "Undertrykk synlige fullføringsvarsler og statusendringer fra
innebygde Codex- eller Claude-underagenter, samtidig som hendelsene deres
beholdes i Feed-telemetri.") so the key
schemaDescriptions.automation.suppressSubagentNotifications in the Norwegian
messages file contains fully translated copy to satisfy the
full-internationalization guideline.
In `@web/messages/pl.json`:
- Line 306: The pl.json entry for the key "suppressSubagentNotifications" is
still in English; replace its value with a Polish translation so the locale is
fully localized — for example: "Ukryj widoczne powiadomienia o zakończeniu i
zmiany statusu od zagnieżdżonych agentów Codex lub Claude, jednocześnie
zachowując ich zdarzenia w telemetrii Feed." Ensure this new string replaces the
existing English text for the "suppressSubagentNotifications" key.
In `@web/messages/pt-BR.json`:
- Around line 304-307: The pt-BR locale still contains the English string for
schemaDescriptions.automation.suppressSubagentNotifications; replace that value
with a proper Brazilian Portuguese translation (e.g., "Suprimir notificações
visíveis de conclusão e alterações de status de agentes filhos aninhados do
Codex ou Claude, mantendo os eventos deles na telemetria do Feed.") so the
user-facing docs/config key
schemaDescriptions.automation.suppressSubagentNotifications is fully localized.
In `@web/messages/ru.json`:
- Around line 304-307: The string at key
schemaDescriptions.automation.suppressSubagentNotifications in ru.json is still
in English; replace it with a proper Russian translation (preserving meaning:
"Suppress visible completion notifications and status mutations from nested
Codex or Claude child agents while keeping their events in Feed telemetry.") and
follow the same punctuation/style used by other locale files (e.g.,
schemaDescriptions.* entries) so the translation matches formatting and i18n
expectations.
In `@web/messages/tr.json`:
- Around line 304-308: The Turkish locale is missing a translation for
schemaDescriptions.automation.suppressSubagentNotifications: replace the English
string currently under
schemaDescriptions.automation.suppressSubagentNotifications with a proper
Turkish translation (e.g. "Gömülü Codex veya Claude alt ajanlarından gelen
görünür tamamlanma bildirimlerini ve durum değişikliklerini gizler, ancak
bunların Feed telemetrisindeki olaylarını korur.") so the tr locale contains a
fully translated entry matching the other high-confidence locales; update the
value for schemaDescriptions.automation.suppressSubagentNotifications
accordingly.
In `@web/messages/uk.json`:
- Line 306: The Ukrainian localization file contains English fallback for the
key "suppressSubagentNotifications"; replace that English string in uk.json with
a proper Ukrainian translation of the message so the UI/docs are fully localized
(update the value for "suppressSubagentNotifications" to an accurate Ukrainian
sentence conveying: suppress visible completion notifications and status
mutations from nested Codex or Claude child agents while keeping their events in
Feed telemetry).
In `@web/messages/zh-CN.json`:
- Around line 304-307: Replace the English value for
schemaDescriptions.automation.suppressSubagentNotifications in
web/messages/zh-CN.json with a proper Simplified Chinese translation; locate the
key "schemaDescriptions.automation.suppressSubagentNotifications" and update its
string to a localized Chinese sentence that conveys "Suppress visible completion
notifications and status mutations from nested Codex or Claude child agents
while keeping their events in Feed telemetry." (e.g., a concise, user-facing
Chinese phrasing).
In `@web/messages/zh-TW.json`:
- Around line 304-307: The string at
schemaDescriptions.automation.suppressSubagentNotifications is still in English;
please provide a Traditional Chinese (zh-TW) translation for that value so the
locale file is fully translated—update the value for
"suppressSubagentNotifications" under "schemaDescriptions" -> "automation" with
a clear zh-TW translation conveying: suppress visible completion notifications
and status changes from nested Codex/Claude child agents while retaining their
events in Feed telemetry.
---
Duplicate comments:
In `@Resources/Localizable.xcstrings`:
- Line 60508: Add the missing Khmer (km) locale entries for the newly added
localization keys that currently have "Suppress Subagent Notifications" (and the
four other new keys noted at the same diff ranges) in
Resources/Localizable.xcstrings by inserting a
"km":{"stringUnit":{"state":"translated","value":"Suppress Subagent
Notifications"}} entry alongside the existing locales; apply the same treatment
(English fallback marked translated) for the other affected diffs referenced
(60512, 60516, 60520, 78587) so each new key includes a km locale.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 95a39bda-bffe-4282-aaed-292e9e3afa92
📒 Files selected for processing (28)
CLI/cmux.swiftResources/Localizable.xcstringsSources/GhosttyTerminalView.swiftSources/Workspace+PanelLifecycle.swiftcmuxTests/CLINotifyProcessIntegrationRegressionTests.swiftcmuxTests/TerminalNotificationClearAllTests.swiftweb/app/[locale]/docs/configuration/page.tsxweb/data/cmux.schema.jsonweb/messages/ar.jsonweb/messages/bs.jsonweb/messages/da.jsonweb/messages/de.jsonweb/messages/en.jsonweb/messages/es.jsonweb/messages/fr.jsonweb/messages/it.jsonweb/messages/ja.jsonweb/messages/km.jsonweb/messages/ko.jsonweb/messages/no.jsonweb/messages/pl.jsonweb/messages/pt-BR.jsonweb/messages/ru.jsonweb/messages/th.jsonweb/messages/tr.jsonweb/messages/uk.jsonweb/messages/zh-CN.jsonweb/messages/zh-TW.json
There was a problem hiding this comment.
16 issues found across 28 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| cwd: cwd, | ||
| transcriptPath: transcriptPath, | ||
| pid: pid, | ||
| launchCommand: launchCommand, | ||
| isRestorable: nil, | ||
| lastSubtitle: nil, | ||
| lastBody: nil, | ||
| lastNotificationStatus: nil, | ||
| updateLastNotificationStatus: false, | ||
| runtimeStatus: runtimeStatus, | ||
| updateRuntimeStatus: updateRuntimeStatus, | ||
| now: now | ||
| ) | ||
| record.activePromptDepth = max(0, record.activePromptDepth ?? 0) + 1 |
There was a problem hiding this comment.
Stale depth counter permanently silences notifications for resumed sessions
activePromptDepth is persisted in ClaudeHookSessionRecord. When a hook fires recordPromptSubmit but the matching recordPromptStop never fires (e.g., the process is killed, the app crashes, or the session is force-quit), the stored depth stays at 1. On the next invocation for the same session ID, recordPromptSubmit increments from 1 → 2 and returns true → nestedPromptEvent: true → shouldSuppressNestedAgentVisibleMutations returns true immediately, before the process ancestry walk that would correctly determine only one agent is present. The depth then oscillates 1 → 2 → 1 → 2 on every subsequent submit/stop cycle, permanently silencing all visible mutations for that session ID. A Codex Teams thread that is killed mid-run will have every future resume silently suppressed with no recovery path short of the session record expiring.
Stale CodeRabbit review. Addressed by later commits through bed1feb; latest CodeRabbit status is successful/skipped, review threads are resolved or outdated, and all required checks are green.

Summary:
Verification:
Note
Medium Risk
Changes core agent-hook lifecycle handling (PID/status/notifications/resume bindings) based on process ancestry and transcript signals, which could unintentionally hide or fail to clear visible state. Risk is mitigated by new regression tests but spans CLI, app runtime, settings, and hook installers.
Overview
Suppresses visible agent-hook side effects (status/PID updates, resume bindings, completion notifications, and raw terminal notifications) for nested/child Codex and Claude sessions, while keeping those events flowing to Feed telemetry.
Adds a default-on
automation.suppressSubagentNotificationssetting (UI, Command Palette,cmux.jsonschema/docs/localizations) and propagates it viaCMUX_SUPPRESS_SUBAGENT_NOTIFICATIONS; introduces ClaudeSubagentStopas feed-only telemetry.Implements nested-session detection via prompt-depth tracking in the session store, managed-subagent startup env (
startup_environmentplumbed through socketpane.create/splits), process ancestry heuristics, and Codex transcript markers to avoid suppressing parent completion when relays are stale. Expands Swift and Python test coverage for nested vs direct stop behavior, settings import/toggle, and hook wiring.Reviewed by Cursor Bugbot for commit bed1feb. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by cubic
Suppresses notifications and status updates from nested Codex/Claude subagents while keeping all events in Feed. Adds a default‑on toggle, feed‑only Claude
SubagentStop, and per‑panel notification suppression.New Features
automation.suppressSubagentNotifications, default true) in Settings, Command Palette, andcmux.json; forwarded viaCMUX_SUPPRESS_SUBAGENT_NOTIFICATIONS. Claude wrapper addsSubagentStopas Feed‑only telemetry. Docs/schema/locales updated.startup_environment/initial_envto mark managed subagents; Workspace uses this to suppress panel‑scoped terminal/desktop notifications.Bug Fixes
SubagentStopwiring.Written for commit bed1feb. Summary will update on new commits. Review in cubic
Summary by CodeRabbit
New Features
Improvements
Tests
Docs & Localization