Skip to content

Render Grok Build PreToolUse Feed decisions in Grok-native shape (#6303) - #6316

Closed
austinywang wants to merge 3 commits into
mainfrom
issue-6303
Closed

austinywang wants to merge 3 commits into
mainfrom
issue-6303

Conversation

@austinywang

@austinywang austinywang commented Jun 17, 2026 •

Copy link
Copy Markdown
Contributor

Closes #6303

Root cause

When a Grok Build PreToolUse hook routes through cmux hooks feed --source grok, cmux blocks waiting for the user's Feed sidebar decision and then writes the decision to stdout for Grok to honor. But renderAgentDecision had no grok branch, so a resolved permission fell through to nonClaudePreToolDecision, which emits Claude-shaped JSON:

{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow",...},"decision":"approve",...}

Grok Build (like Antigravity, and per its hook docs) only honors a native blocking decision of the shape:

{"decision":"allow"|"deny","reason":"…"}

It does not understand hookSpecificOutput.permissionDecision nor the "approve"/"block" values. So an approved Write/Bash was never recognized, and the hook only ever cleared via the 120s fail-open timeout — adding ~2 minutes of dead time to every side-effecting tool call.

Fix

Route source == "grok" through the existing Antigravity native-decision branch in renderAgentDecision (CLI/cmux.swift), so a resolved Feed permission emits {"decision":"allow"|"deny","reason":…}. One-line, mirrors the already-shipped antigravity handling; no behavior change for any other agent.

This is root cause #3 from the issue. The other suggested items (single pretool wrapper, dedup of stacked feed hooks, side-effecting tool aliases for Grok-native lowercase tool names) are deliberately out of scope here: the reported repro uses Cursor-style Write/Bash, which already classify correctly, and the duplicate-hook/pruning work involves the user-side hook-install architecture rather than a clean cmux code fix. This PR fixes the unambiguous, on-the-repro-path decision-format mismatch.

Verification

Two-commit red/green structure:

  • Commit 1 adds cmuxTests/CLIGrokFeedDecisionTests.swift (wired into cmux.xcodeproj), which drives the real bundled CLI against a mock Feed socket that resolves a pending permission, and asserts the emitted stdout is the Grok-native shape (decision == allow/deny, no hookSpecificOutput). It is red without the fix.
  • Commit 2 adds the one-line fix; the test goes green.

Note: local reload.sh build/verification was repeatedly interrupted by external churn of the fleet worktree for this issue (the worktree directory was deleted mid-session more than once). The branch is pushed and the regression test is wired into CI; the red commit-1 / green commit-2 statuses will demonstrate the test genuinely catches the bug.

🤖 Generated with Claude Code


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Summary by cubic

Fixes Grok PreToolUse decision formatting so approved tool calls unblock immediately, and adds a per-pane runaway-memory guardrail to prevent one leaking process from freezing the app. Fixes #6303; addresses #6313.

  • New Features

    • Added a per-pane runaway-memory guardrail (on by default). Warns when a pane’s process tree exceeds a configurable threshold (8 GB default) and clears with hysteresis.
    • Shows an orange sidebar badge and a top banner with pane/workspace, current memory, and foreground command. Includes a confirmable “Kill Process” action that targets the foreground process group while keeping the shell open.
    • Terminal settings: toggle and GB threshold. Localized copy and accessibility labels included.
  • Bug Fixes

    • For --source grok PreToolUse hooks, emit Grok-native decisions: {"decision":"allow"|"deny","reason":...}. This removes the 120s fail-open delay and matches the Antigravity path.
    • Added regression tests to verify the Grok decision shape and new memory guardrail engine.

Written for commit 3b36084. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

Release Notes

  • New Features

    • Added a pane memory guardrail that monitors terminal memory usage and displays warnings when thresholds are exceeded.
    • New terminal settings: enable/disable memory guardrail and configure the memory warning threshold (in GB).
    • Memory warning banner with options to dismiss or terminate the runaway process.
    • Sidebar indicator for workspaces with active memory warnings.
  • Tests

    • Added test coverage for memory guardrail functionality and feed decision handling.

austinywang and others added 3 commits June 17, 2026 11:22
A single pane running a leaking process (e.g. uv run pytest growing to
~14 GB RSS) makes macOS aggregate the child memory under the app, report
hundreds of GB, declare "out of application memory", and OOM-suspend the
whole app — killing every other healthy pane with no prior signal.

This adds a per-pane guardrail that catches a runaway tree at the pane
level first:

- A background timer (PaneMemoryGuardrail) polls every live pane ~every
  4s. It attributes process-tree memory by the pane's controlling tty:
  every process under the pane (shell + descendants + background jobs)
  shares the tty, so it sums physical-footprint bytes across all pids on
  that tty device via the existing CmuxTopProcessSnapshot libproc walk.
- When a pane crosses a configurable threshold (default 8 GB) it
  edge-triggers an orange warning badge on the workspace tab and a
  dismissible banner identifying the pane, its process-tree memory, and
  the foreground command. Hysteresis clears at 0.8x threshold; the banner
  fires once per crossing and re-arms after it clears.
- The banner's "Kill Pane Process" action (with confirm) sends SIGTERM
  then SIGKILL to the pane's foreground process group, leaving the shell
  alive; falls back to closing the pane when there is no foreground group.
- New Terminal settings: enable toggle + threshold (GB), default on / 8 GB.
- Below threshold it stays completely silent (no always-on memory UI).

Ghostty foreground-pid / tty-name accessors added on TerminalSurface.
Pure edge-trigger engine unit-tested (PaneMemoryGuardrailTests).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
cmux hooks feed --source grok currently emits Claude-shaped
nonClaudePreToolDecision JSON for a resolved Feed permission, which Grok
Build cannot parse — so the PreToolUse hook only clears via the 120s
fail-open timeout. This test asserts the Grok-native
{"decision":"allow|deny","reason":...} shape and fails until the fix lands.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Route source==grok through the Antigravity native-decision branch so a
resolved Feed permission emits {"decision":"allow|deny","reason":...}
instead of Claude's hookSpecificOutput/"approve" shape that Grok cannot
parse. This lets an approved Write/Bash clear immediately instead of
waiting out the 120s PreToolUse fail-open timeout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 17, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jun 17, 2026 6:41pm
cmux-staging Ready Ready Preview, Comment Jun 17, 2026 6:41pm

@coderabbitai

coderabbitai Bot commented Jun 17, 2026 •

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Fixes Grok PreToolUse feed decisions by extending the existing Antigravity native-shape branch in CLI/cmux.swift to also match source == "grok". Separately introduces a per-pane runaway-memory guardrail: a hysteresis engine, background scan coordinator, SwiftUI warning banner, sidebar warning badge, settings keys and UI, localization strings, and unit tests.

Changes

Grok Feed Decision Fix (issue #6303)

Layer / File(s) Summary
CLI source gate expansion and regression tests
CLI/cmux.swift, cmuxTests/CLIGrokFeedDecisionTests.swift, cmux.xcodeproj/project.pbxproj
The PreToolUse permission-decision branch gains source == "grok" alongside the existing "antigravity" check. A regression test suite runs the bundled CLI against a mock Unix-socket feed server for both allow and deny modes, asserting Grok-native decision/reason output and the absence of hookSpecificOutput. The project file registers the new test file.

Pane Memory Guardrail

Layer / File(s) Summary
Settings keys and TerminalSurface process introspection
Packages/CmuxSettings/.../TerminalCatalogSection.swift, Packages/CmuxTerminal/.../TerminalSurface+ProcessInfo.swift
TerminalCatalogSection adds runawayMemoryGuardrailEnabled (bool, default on) and runawayMemoryGuardrailThresholdGB (double, default 8). A new TerminalSurface extension exposes foregroundProcessID() and controllingTTYName() by querying the live Ghostty surface with proper memory management.
Guardrail data model, hysteresis engine, process killer, and coordinator
Sources/PaneMemoryGuardrail.swift
Defines PaneMemoryPaneKey, PaneMemoryDescriptor, PaneMemorySample, and PaneMemoryWarning value types. PaneMemoryGuardrailEngine implements edge-triggered warn/clear hysteresis with dismiss/acknowledge state. PaneMemoryProcessKiller sends SIGTERM then SIGKILL after a grace period. The @MainActor PaneMemoryGuardrail coordinator runs a dispatch timer, fetches live pane descriptors, computes off-main process samples via CmuxTopProcessSnapshot, updates the active banner, and exposes dismissActiveBanner() and killActivePaneProcess().
SidebarUnreadModel memory warning state and AppDelegate startup wiring
Sources/TerminalNotificationStore.swift, Sources/AppDelegate.swift
SidebarUnreadModel gains a @Published memoryWarningWorkspaceIds set with equality-guarded setter and hasMemoryWarning(forWorkspaceId:) query. AppDelegate.configure calls startPaneMemoryGuardrailIfNeeded, which injects a pane provider enumerating live terminal surfaces, propagates warned workspace IDs to sidebarUnread, and handles pane close requests.
Banner view, ContentView integration, and localization
Sources/PaneMemoryGuardrailBannerView.swift, Sources/ContentView.swift, Resources/Localizable.xcstrings
PaneMemoryGuardrailBanner renders an animated, dismissible warning card with kill/dismiss actions and a destructive confirmation dialog. ContentView overlays the banner at the top of the terminal area and adds a per-workspace orange exclamationmark.triangle.fill icon to TabItemView (including Equatable and stored property updates). Localization keys cover the banner, kill confirmation dialog, sidebar tooltip/accessibility, and settings copy.
Settings UI rows, guardrail unit tests, and project wiring
Packages/CmuxSettingsUI/.../TerminalSection.swift, cmuxTests/PaneMemoryGuardrailTests.swift, cmux.xcodeproj/project.pbxproj
TerminalSection adds a toggle and stepper for the guardrail, backed by new @State DefaultsValueModel properties; the stepper is disabled when the toggle is off. PaneMemoryGuardrailTests validates engine behavioral scenarios and TTY-based process memory attribution. The project file registers the new test file.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(255, 165, 0, 0.5)
    Note over AppDelegate,PaneMemoryGuardrail: App Startup
  end
  AppDelegate->>PaneMemoryGuardrail: startPaneMemoryGuardrailIfNeeded (configure + start)

  rect rgba(100, 149, 237, 0.5)
    Note over PaneMemoryGuardrail,CmuxTopProcessSnapshot: Periodic Scan Tick
  end
  PaneMemoryGuardrail->>PaneMemoryGuardrail: paneProvider() → [PaneMemoryDescriptor]
  PaneMemoryGuardrail->>CmuxTopProcessSnapshot: compute process-tree memory off-main
  CmuxTopProcessSnapshot-->>PaneMemoryGuardrail: [PaneMemorySample]
  PaneMemoryGuardrail->>PaneMemoryGuardrailEngine: ingest(samples)
  PaneMemoryGuardrailEngine-->>PaneMemoryGuardrail: newBanners, warnedIds, clearedPanes
  PaneMemoryGuardrail->>SidebarUnreadModel: setMemoryWarningWorkspaceIds(warnedIds)
  PaneMemoryGuardrail->>PaneMemoryGuardrailBanner: publish activeBanner

  rect rgba(220, 20, 60, 0.5)
    Note over PaneMemoryGuardrailBanner,PaneMemoryProcessKiller: User Kill Action
  end
  PaneMemoryGuardrailBanner->>PaneMemoryGuardrail: killActivePaneProcess()
  PaneMemoryGuardrail->>PaneMemoryProcessKiller: killProcessGroups(pgids)
  PaneMemoryProcessKiller->>PaneMemoryProcessKiller: SIGTERM → grace period → SIGKILL
  PaneMemoryGuardrail->>PaneMemoryGuardrailEngine: acknowledge(paneKey)
  PaneMemoryGuardrail->>SidebarUnreadModel: setMemoryWarningWorkspaceIds(updated)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • manaflow-ai/cmux#4562: CI/lint that verifies cmuxTests/*.swift files are correctly wired into the target's Sources build phase, directly related to the new test files added to cmux.xcodeproj/project.pbxproj here.
  • manaflow-ai/cmux#5010: Also modifies CLI/cmux.swift's feed/event permission classification for PreToolUse, directly related to the Grok source gate expansion in this PR.
  • manaflow-ai/cmux#5859: Introduced SidebarUnreadModel and the sidebar observation path that this PR extends with memoryWarningWorkspaceIds and hasMemoryWarning(forWorkspaceId:).

Poem

🐇 Hop hop, the memory grows too tall,
A banner pops to warn before the fall!
SIGTERM first, then SIGKILL if it stays,
And Grok now speaks its own JSON phrase.
No more Claude shapes where Grok should be—
The rabbit fixed it, twitching ears with glee! 🎉


Important

Pre-merge checks failed

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

❌ Failed checks (5 errors, 1 warning)

Check name Status Explanation Resolution
Cmux Swift Blocking Runtime ❌ Error PR introduces timing-based dispatch: DispatchQueue.global().asyncAfter(deadline: .now() + graceSeconds) in PaneMemoryProcessKiller.terminate() uses delayed dispatch for SIGKILL after grace period... Replace 3-second asyncAfter delay with real process state signal (e.g., monitor process exit, receive notification from parent/debugger, or use platform APIs for grace-period coordination) instead of timing-based dispatch.
Cmux Swift Concurrency ❌ Error PR introduces legacy async patterns: custom DispatchQueue for ordinary off-main work (line 195), DispatchSourceTimer periodic polling (line 203), fire-and-forget Tasks from timer callbacks (line 21... Replace custom DispatchQueue+DispatchSourceTimer with async Timer or task loop using Task(priority:); convert fire-and-forget Tasks to structured concurrency; replace Combine @Published in SidebarUnreadModel with @Observable macro or asy...
Cmux Swift @Concurrent ❌ Error The closure assigned to paneProvider in AppDelegate.startPaneMemoryGuardrailIfNeeded() calls @MainActor methods (controllingTTYName, foregroundProcessID) but lacks explicit @MainActor annotation... Add explicit @MainActor annotation to the paneProvider closure: guardrail.paneProvider = { [weak tabManager] @MainActor in ... }
Cmux Swiftui State Layout ❌ Error New PaneMemoryGuardrail class uses ObservableObject instead of @Observable, the modern cmux pattern already used elsewhere (TaskManagerWindowController, MobilePairingModel, etc.) Replace ObservableObject with @Observable in PaneMemoryGuardrail to match modern cmux state patterns.
Cmux Architecture Rethink ❌ Error PR duplicates user defaults keys between PaneMemoryGuardrail.swift and TerminalCatalogSection.swift, creating competing sources of truth for "terminal.runawayMemoryGuardrail.*" settings, risking si... Reference TerminalCatalogSection's DefaultsKey definitions in PaneMemoryGuardrail instead of hardcoding duplicate strings, or expose them via extension for unified source of truth.
Docstring Coverage ⚠️ Warning Docstring coverage is 32.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (15 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main fix: routing Grok Build PreToolUse Feed decisions through the native shape handler, addressing the core issue in #6303.
Description check ✅ Passed The description comprehensively covers root cause analysis, the specific fix, and verification approach with red/green test commits, addressing the template's Summary and Testing sections.
Linked Issues check ✅ Passed The PR directly addresses root cause #3 (decision format mismatch) from #6303 by routing grok through native-decision branch. It also implements the memory guardrail feature addressing #6313, plus adds regression tests.
Out of Scope Changes check ✅ Passed The PR focuses on the decision format fix (#6303 root cause #3) and adds a separate runaway-memory guardrail feature. The memory guardrail is comprehensively implemented across settings, UI, core logic, and tests, all within stated objectives.
Cmux Swift Actor Isolation ✅ Passed Production Swift changes maintain proper actor isolation. PaneMemoryGuardrail is @MainActor ObservableObject; all value types (PaneMemoryDescriptor, PaneMemorySample, etc.) are Sendable; TerminalSu...
Cmux Expensive Synchronous Load ✅ Passed PR adds PaneMemoryGuardrail with expensive process-snapshot operations isolated to nonisolated static method running off-main via background DispatchQueue; main-actor paths only read fast libghostt...
Cmux Cache Substitution Correctness ✅ Passed No cache substitution correctness violations found. PaneMemoryGuardrail reads defaults fresh from UserDefaults.standard (not cached); lastSamplesByKey is a transient UI cache updated every tick (al...
Cmux No Hacky Sleeps ✅ Passed Check not applicable: PR contains only Swift code, localization, and Xcode project files. The rule covers only non-Swift runtime changes (TypeScript, JavaScript, shell), which are not present.
Cmux Algorithmic Complexity ✅ Passed All algorithmic complexity requirements satisfied: nested paneProvider iteration O(W×P) scans FIXED app structure (not user-scalable), background process scanning properly delegated, main-thread op...
Cmux Swift File And Package Boundaries ✅ Passed Both new production files are under the 400-line threshold (346 and 156 lines) with single, clear responsibilities; existing oversized files received only +41 and +24 lines (well under the 250-line...
Cmux Swift Logging ✅ Passed No logging violations found. New/modified production code contains no print/debugPrint/dump/NSLog calls; existing print() in CLI is allowed user output; pre-existing NSLog calls unchanged.
Cmux User-Facing Error Privacy ✅ Passed All user-facing text (CLI decision output, UI strings, error messages) avoids vendor names, internal details, credentials, and raw upstream messages. Strings are generic and user-safe.
Cmux Full Internationalization ✅ Passed All user-facing Swift text in production changes uses String(localized:defaultValue:) with matching translated entries in Resources/Localizable.xcstrings for all supported locales (en, ja).
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR adds PaneMemoryGuardrailBanner (SwiftUI View overlay on main window content), not a standalone window/panel/WindowGroup. Lint script confirms no violations in cmuxAuxiliaryWindowIdentifiers.
Cmux Source Artifacts ✅ Passed All changed paths are intentional source files (Swift), tests, configuration (Xcode project), or resource files (localization strings). No artifacts (logs, temp folders, caches, build output, depen...
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-6303
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch issue-6303

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 Jun 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR delivers two independent changes: a one-line fix routing --source grok PreToolUse hook decisions through the Antigravity native-decision path (removing the 120 s fail-open delay), and a new per-pane runaway-memory guardrail that polls process-tree memory by controlling TTY, badges the sidebar, and surfaces a dismissible banner with a confirmable kill action.

  • Grok fix (CLI/cmux.swift): changes source == \"antigravity\" to source == \"antigravity\" || source == \"grok\", emitting {\"decision\":\"allow\"|\"deny\",\"reason\":\"...\"} instead of the Claude-shaped output Grok cannot parse. Backed by a red/green integration test against a mock Feed socket.
  • Memory guardrail (PaneMemoryGuardrail.swift, PaneMemoryGuardrailBannerView.swift, AppDelegate.swift, ContentView.swift, TerminalNotificationStore.swift): pure PaneMemoryGuardrailEngine value type handles edge-trigger + hysteresis logic cleanly; @MainActor PaneMemoryGuardrail drives the DispatchSourceTimer poll and coordinates with SidebarUnreadModel. PaneMemoryProcessKiller.terminate uses DispatchQueue.global.asyncAfter (both flagged patterns); the kill confirmation dialog can also target the wrong pane if activeBanner changes during the dialog's open window.

Confidence Score: 3/5

The Grok fix and memory guardrail engine are safe, but the kill confirmation dialog can send SIGTERM/SIGKILL to the wrong pane if the active banner changes during the confirmation window, and the SIGKILL grace-period dispatch uses a flagged legacy pattern.

The Grok routing fix is a trivially correct one-liner. The memory guardrail engine logic and SwiftUI wiring are solid. Two issues in the guardrail reduce confidence: the kill confirmation dialog reads activeBanner at invocation time rather than at dialog-open time, so a concurrent polling tick could swap the target pane silently, and PaneMemoryProcessKiller.terminate uses DispatchQueue.global.asyncAfter — a wall-clock timing delay via the legacy dispatch API — where Swift concurrency is the correct shape per the codebase's stated rules.

Sources/PaneMemoryGuardrailBannerView.swift (kill confirmation targets wrong pane if banner changes) and Sources/PaneMemoryGuardrail.swift (asyncAfter + DispatchQueue.global in PaneMemoryProcessKiller).

Important Files Changed

Filename Overview
CLI/cmux.swift One-line fix routing source == "grok" through the Antigravity native-decision branch, emitting `{"decision":"allow"
Sources/PaneMemoryGuardrail.swift New 346-line production file. PaneMemoryGuardrailEngine value type and edge-trigger logic are clean. PaneMemoryProcessKiller.terminate introduces new DispatchQueue.global(qos:.userInitiated).asyncAfter — a legacy pattern and a wall-clock timing delay flagged by both the concurrency-modernization and blocking-runtime rules. The @MainActor isolation on PaneMemoryGuardrail is correct, but it uses ObservableObject/@Published where @Observable is the current preferred shape.
Sources/PaneMemoryGuardrailBannerView.swift SwiftUI banner view with confirmation dialog. Kill confirmation captures guardrail.killActivePaneProcess() at invocation time, not at dialog-open time, so if activeBanner changes to a different pane between opening and confirming the dialog, the wrong pane's process group is sent SIGTERM/SIGKILL.
Sources/AppDelegate.swift Wires up PaneMemoryGuardrail at app startup; correct weak capture of tabManager and notificationStore to avoid retain cycles.
Sources/ContentView.swift Adds hasMemoryWarning precomputed snapshot field to TabItemView and orange badge icon, plus the PaneMemoryGuardrailBanner overlay; follows the existing snapshot-boundary pattern correctly.
Resources/Localizable.xcstrings All new string keys include both en and ja translations, matching the existing two-locale catalog; no missing entries.
cmuxTests/CLIGrokFeedDecisionTests.swift Integration test driving the real CLI against a mock Feed socket; correctly asserts native Grok shape and absence of hookSpecificOutput. DispatchQueue.global use here is test-only scaffolding (exempt by rule).
cmuxTests/PaneMemoryGuardrailTests.swift Unit tests for the pure PaneMemoryGuardrailEngine covering all edge-trigger, hysteresis, dismiss, clear, and multi-crossing scenarios. Well-structured and complete.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant G as Grok Build
    participant H as cmux hooks feed --source grok
    participant F as Feed socket (cmux app)
    participant U as User (Feed sidebar)

    G->>H: stdin: PreToolUse JSON
    H->>F: feed.push (blocks waiting for decision)
    F->>U: show permission card
    U->>F: approve / deny
    F->>H: "resolved decision {kind:permission, mode:once|deny}"
    Note over H: renderAgentDecision() source=="grok" native branch
    H->>G: "stdout: {"decision":"allow","reason":"..."}"
    Note over G: Unblocks immediately (was: 120s timeout)
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant G as Grok Build
    participant H as cmux hooks feed --source grok
    participant F as Feed socket (cmux app)
    participant U as User (Feed sidebar)

    G->>H: stdin: PreToolUse JSON
    H->>F: feed.push (blocks waiting for decision)
    F->>U: show permission card
    U->>F: approve / deny
    F->>H: "resolved decision {kind:permission, mode:once|deny}"
    Note over H: renderAgentDecision() source=="grok" native branch
    H->>G: "stdout: {"decision":"allow","reason":"..."}"
    Note over G: Unblocks immediately (was: 120s timeout)
Loading

Reviews (1): Last reviewed commit: "Render Grok Feed decisions in Grok-nativ..." | Re-trigger Greptile

Comment on lines +157 to +161
DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + graceSeconds) {
for pgid in pgids {
_ = kill(pid_t(-pgid), SIGKILL)
}
}

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.

P1 DispatchQueue.global and asyncAfter are both flagged patterns in new production Swift. asyncAfter is a wall-clock timing primitive where either a DispatchSourceProcess (fire on process exit) or Swift concurrency (Task { try? await Task.sleep(for: .seconds(graceSeconds)); ... }) is the correct shape. DispatchQueue.global for ordinary async work should also be replaced with a Swift concurrency Task. The minimal mechanical fix switches to Swift concurrency; the more robust fix would use a DispatchSourceProcess to watch for the process to exit after SIGTERM and only escalate if it hasn't.

Suggested change
DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + graceSeconds) {
for pgid in pgids {
_ = kill(pid_t(-pgid), SIGKILL)
}
}
Task.detached(priority: .userInitiated) {
try? await Task.sleep(for: .seconds(graceSeconds))
for pgid in pgids {
_ = kill(pid_t(-pgid), SIGKILL)
}
}

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +57 to +91
Button(role: .destructive) {
isConfirmingKill = true
} label: {
Text(String(
localized: "paneMemoryGuardrail.banner.kill",
defaultValue: "Kill Pane Process"
))
}
.buttonStyle(.borderedProminent)
.controlSize(.small)
.confirmationDialog(
String(
localized: "paneMemoryGuardrail.confirm.title",
defaultValue: "Kill this pane's runaway process?"
),
isPresented: $isConfirmingKill,
titleVisibility: .visible
) {
Button(role: .destructive) {
guardrail.killActivePaneProcess()
} label: {
Text(String(
localized: "paneMemoryGuardrail.confirm.kill",
defaultValue: "Kill Process"
))
}
Button(role: .cancel) {} label: {
Text(String(localized: "paneMemoryGuardrail.confirm.cancel", defaultValue: "Cancel"))
}
} message: {
Text(String(
localized: "paneMemoryGuardrail.confirm.message",
defaultValue: "This sends SIGTERM then SIGKILL to the foreground process group in the pane. The shell stays open."
))
}

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.

P1 Kill confirmation targets wrong pane if banner switches during dialog

isConfirmingKill is @State on the view and persists through re-renders. If a second pane crosses the threshold within the 4-second poll window while this dialog is open, activeBanner updates to that new pane. The user sees the dialog they opened for pane A, confirms, and killActivePaneProcess() reads the current activeBanner — silently sending SIGTERM/SIGKILL to pane B's foreground process group. Capturing the target pane's key at the moment the Kill button is pressed and passing it through to the kill call avoids the race.

Comment on lines +171 to +172
@MainActor
final class PaneMemoryGuardrail: ObservableObject {

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 PaneMemoryGuardrail is a new @MainActor final class; @Observable (Swift 5.9+) is the current preferred shape for this pattern in the codebase. ObservableObject/@Published causes whole-view invalidation on any published change whereas @Observable gives property-granular tracking and removes the need for @ObservedObject at the call site.

Suggested change
@MainActor
final class PaneMemoryGuardrail: ObservableObject {
@MainActor
@Observable
final class PaneMemoryGuardrail {

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@austinywang

Copy link
Copy Markdown
Contributor Author

Superseding this PR per maintainer decision: it was produced by a fleet worker that was interrupted mid-run (its worktree was torn down before the build/verify step), so it's likely unverified. A fresh worker is redoing #6303 cleanly with a verified build and will open a replacement PR. Reopen if this work is preferred.

This branch was successfully deployed

1 active deployment
Preview – cmux — 3b36084a Deployed Jun 17, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Grok Build PreToolUse: 120s Feed timeout on Write + decision format mismatch

1 participant