Skip to content

Fix 8 macOS issues + resolve all Dependabot security vulnerabilities - #1822

Closed
arzafran wants to merge 5 commits into
manaflow-ai:mainfrom
darkroomengineering:fix/macos-issues-and-deps
Closed

arzafran wants to merge 5 commits into
manaflow-ai:mainfrom
darkroomengineering:fix/macos-issues-and-deps

Conversation

@arzafran

@arzafran arzafran commented Mar 19, 2026 •

Copy link
Copy Markdown

Summary

Community contribution addressing 8 open macOS issues and all 7 Dependabot security alerts.

Bug Fixes

SIGSEGV crash in inheritedTerminalConfig (#1558, #1608, #1767, #1815)

  • Added isSurfaceLive guard on TerminalSurface that checks both non-nil pointer AND live portal lifecycle state before passing the surface to any Ghostty C API. Prevents use-after-free when the surface is in async teardown.

"TabManager not available" hook errors (#1775, #1715)

  • Lifecycle hooks (stop, session-end, notification, prompt-submit) now degrade gracefully when TabManager is already torn down during app/workspace close. Only session-start requires a live workspace.

CLI shim --session-id error in fresh tabs (#1818)

  • The claude wrapper now verifies the resolved binary supports --session-id via --help before using it. Falls back to hooks-only mode if the flag isn't supported (e.g., stale nvm-resolved binary).

Side panel toggle affects wrong window (#1779)

  • Added toggleSidebarForWindow(_:) to AppDelegate. Titlebar accessory buttons now capture their own view.window via a WeakSelfBox pattern, targeting the correct window instead of whichever happens to be key.

Zoomed pane doesn't un-zoom on focus navigation (#1605)

  • moveFocus(direction:) now calls clearSplitZoom() when a pane is zoomed, matching tmux behavior.

Bash job termination notification spam (#1565)

  • Replaced { ... } & disown with ( ... ) & for all background telemetry commands. Subshells are never added to bash's job table, eliminating spurious "Done" notifications.

Equalize Splits produces uneven layouts (#1622)

  • Added countLeaves(in:along:) to calculate proportional divider positions based on leaf pane count per axis. A | (B | C) now produces 33/33/33 instead of 50/25/25.

Menu order — Ghostty Settings above Settings (#1680)

  • Moved "Ghostty Settings..." and "Reload Configuration" from .appInfo into .appSettings, placing them after the Preferences item.

Security (Dependabot)

Resolves all 7 open Dependabot alerts (3 high, 3 moderate, 1 low):

Package From To Severity Issue
next 16.1.6 16.1.7 Medium HTTP smuggling, CSRF bypass, DoS
flatted 3.3.3 3.4.2 High Prototype pollution, unbounded recursion DoS
dompurify 3.3.1 3.3.3 Medium XSS
minimatch 3.1.2 3.1.5 High ReDoS via combinatorial backtracking
minimatch (nested) 9.0.5 9.0.9 High ReDoS via combinatorial backtracking

Files Changed

File Change
Sources/GhosttyTerminalView.swift isSurfaceLive computed property
Sources/Workspace.swift Surface lifecycle guards + un-zoom on focus nav
CLI/cmux.swift Graceful hook degradation
Resources/bin/claude --session-id compatibility check
Sources/AppDelegate.swift toggleSidebarForWindow(_:)
Sources/Update/UpdateTitlebarAccessory.swift WeakSelfBox window targeting
Sources/TabManager.swift Proportional leaf-counting equalize
Sources/cmuxApp.swift Menu item reordering
Resources/shell-integration/cmux-bash-integration.bash Subshell background jobs
web/package.json next + eslint-config-next bump
web/package-lock.json All transitive dependency updates

Summary by cubic

Fixes eight macOS bugs to improve stability and window behavior, and closes all Dependabot security alerts by upgrading vulnerable web dependencies.

  • Bug Fixes

    • Prevented SIGSEGV in inherited terminal config by guarding C API calls with isSurfaceLive.
    • Lifecycle hooks now degrade gracefully if TabManager is torn down; only session-start and active require a live workspace. No fallback to other workspaces, and notifications/status updates are best‑effort.
    • claude wrapper verifies --session-id support via --help, caches the check per binary path+mtime, and falls back to hooks‑only mode if unsupported.
    • Sidebar toggle buttons target their own window (WeakSelfBox + toggleSidebarForWindow(_:)), not the key window.
    • Focus navigation clears split zoom first, primes browser portal host replacement, then reconciles layout/portals for consistent visibility, matching tmux behavior.
    • Suppressed bash “Done” notifications by running background commands in a subshell and disowning the job.
    • Equalize Splits uses leaf‑counted proportions along the axis for even layouts in nested splits.
    • Moved “Ghostty Settings…” and “Reload Configuration” below Preferences in the app menu.
  • Dependencies

    • Resolved security alerts by upgrading:
      • next 16.1.6 → 16.1.7
      • flatted 3.3.3 → 3.4.2
      • dompurify 3.3.1 → 3.3.3
      • minimatch 3.1.2 → 3.1.5 and 9.0.5 → 9.0.9
    • Also aligned eslint-config-next to 16.1.7; lockfile updated with safe transitive versions.

Written for commit 2e8222a. Summary will update on new commits.

Summary by CodeRabbit

  • Bug Fixes

    • More resilient workspace/session lifecycle and notifications: failed resolutions now no longer surface errors and often return success.
    • Safer terminal/surface handling during teardown and improved split-pane navigation when zoomed.
    • More robust background shell jobs with suppressed disown errors.
    • Prevented targeting of unavailable workspaces when probes fail.
  • New Features

    • Window-context-aware sidebar toggle.
  • Chores

    • Session ID injection now performed only when the target tool supports it (with caching).
    • Updated web build dependencies and adjusted app menu ordering.

@vercel

vercel Bot commented Mar 19, 2026

Copy link
Copy Markdown

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

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Mar 19, 2026 •

Copy link
Copy Markdown

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Guards and best-effort behavior added across CLI hooks and shell integration; conditional session-id injection in the claude wrapper; window-scoped sidebar toggle; TerminalSurface liveness predicate; split-equalization now counts leaves; split-zoom clearing/refinement and portal visibility reconciliation updated.

Changes

Cohort / File(s) Summary
Claude hook & CLI
CLI/cmux.swift
Made workspace/surface resolution more tolerant: many claude-hook side-effects are now best-effort (use try? / early print("OK") and return). resolveWorkspaceIdForClaudeHook now throws when a candidate workspace fails surface.list, preventing silent fallback to workspace.current.
Claude wrapper launcher
Resources/bin/claude
Detect --session-id support by probing "$REAL_CLAUDE" --help (cached by binary path + mtime) and only inject --session-id when supported; otherwise fall back to --settings only. Preserves existing SKIP_SESSION_ID behavior.
Shell integration (background jobs)
Resources/shell-integration/cmux-bash-integration.bash
Convert background async jobs to subshell-wrapped invocations ( ... ) & and unify disown 2>/dev/null usage; ensure backgrounded early-exit uses exit 0 inside subshells.
Window / sidebar controls
Sources/AppDelegate.swift, Sources/Update/UpdateTitlebarAccessory.swift
Add AppDelegate.toggleSidebarForWindow(_:); titlebar accessory defers to resolved NSWindow and calls window-scoped toggle, using a WeakSelfBox to capture vc weakly.
Terminal surface lifecycle
Sources/GhosttyTerminalView.swift
Add TerminalSurface.isSurfaceLive predicate to guard use of the Ghostty surface pointer during portal lifecycle transitions.
Workspace & split-zoom
Sources/Workspace.swift
Change clearSplitZoom() → clearSplitZoom(reason: String = ...); clear split-zoom before focus moves, prime portal-host replacement during un-zoom, and centralize portal visibility reconciliation + layout follow-up.
Split equalization
Sources/TabManager.swift
equalizeSplits computes divider position by counting leaf panes along the split axis via a new leaf-count helper instead of using a fixed 0.5.
App commands & deps
Sources/cmuxApp.swift, web/package.json
Reordered .appInfo command groups (moved “About cmux” next to update items); bumped next and eslint-config-next from 16.1.6 → 16.1.7.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐇 I tiptoe through hooks and hop through panes,

Session-ids checked, then whispered refrains,
Best-effort sends and gentle returns,
Sidebars toggle where the warm window burns,
Leaves count splits — the rabbit applauds the gains.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately captures the main objectives: fixing 8 macOS issues and resolving all Dependabot security vulnerabilities, matching the changeset scope.
Description check ✅ Passed The description provides comprehensive coverage: detailed bug fixes with issue numbers, security updates with a severity table, and files changed. However, Testing and Demo Video sections are missing from the template structure.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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.

Tip

CodeRabbit can suggest fixes for GitHub Check annotations.

Configure the reviews.tools.github-checks setting to adjust the time to wait for GitHub Checks to complete.

@greptile-apps

greptile-apps Bot commented Mar 19, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This community PR addresses 8 open macOS issues (crashes, hook errors, UI bugs, and shell noise) alongside all 7 open Dependabot security alerts. The bulk of the Swift changes are well-targeted crash fixes (isSurfaceLive guard, WeakSelfBox window-targeting, un-zoom on focus navigation, proportional equalize-splits) and the security dependency bumps are straightforward. Two areas warrant attention before merging:

  • cmux-bash-integration.bash — disown removal: The fix for "Done" job-notification spam replaces { } & disown with ( ) &, claiming subshells are never added to bash's job table. This is incorrect — both syntaxes produce tracked job table entries; it was the disown that suppressed the notifications. For the short-lived telemetry helpers the practical impact may be minor (PROMPT_COMMAND timing can suppress notifications), but for _cmux_start_pr_poll_loop the disown was load-bearing: removing it means the long-running poll loop is now in the job table, and shells with huponexit enabled will kill it on normal exit rather than letting _cmux_bash_cleanup handle it.
  • Resources/bin/claude — per-invocation --help probe: The --session-id capability check spawns the real claude binary (with full nvm/Node startup cost) on every single invocation. For most users this flag is supported, so the check is paid every time for a result that never changes. Caching by binary path/mtime would reduce this to a one-time cost per session.

Key changes:

Confidence Score: 3/5

  • Safe to merge with minor follow-up; the Swift crash fixes and security updates are solid, but the bash disown removal carries a correctness risk for huponexit environments and the --help probe adds per-invocation overhead.
  • The Swift fixes are well-reasoned and the security bumps are straightforward. However, cmux-bash-integration.bash removes disown from a long-running background loop based on an incorrect premise (subshells ARE in the job table), which can silently break the PR-poll loop in shells with huponexit. The Resources/bin/claude --help check is correct but unconditional and adds measurable latency every invocation. These two issues lower confidence from what would otherwise be a 4.
  • Resources/shell-integration/cmux-bash-integration.bash (disown removal from poll loop) and Resources/bin/claude (per-invocation --help overhead) need the most attention before merging.

Important Files Changed

Filename Overview
CLI/cmux.swift Adds graceful degradation for lifecycle hooks when TabManager is torn down; logic is sound but the inline comment misstates which subcommands need a live workspace (omits active).
Resources/bin/claude Adds --session-id capability check via --help before each invocation; the correctness fix for stale binaries is good but the check is unconditional and adds measurable latency on every claude run.
Resources/shell-integration/cmux-bash-integration.bash Replaces { } & disown with ( ) & for background jobs; the stated rationale (subshells not added to job table) is incorrect, and removing disown from the long-running PR poll loop changes HUP-signal behavior that could cause premature termination.
Sources/GhosttyTerminalView.swift Adds isSurfaceLive computed property guarding both non-nil pointer and live portal lifecycle state; well-documented and directly addresses the use-after-free crash.
Sources/TabManager.swift Adds countLeaves(in:along:) for proportional equalize-splits; the recursive algorithm correctly handles mixed-axis trees (e.g., A
Sources/Update/UpdateTitlebarAccessory.swift Introduces WeakSelfBox pattern so the sidebar toggle closure resolves view.window at call-time rather than capturing the (potentially wrong) key window at construction time; weakSelfBox.vc is correctly assigned after super.init().
Sources/Workspace.swift Applies isSurfaceLive guards before C API calls (fixing SIGSEGV) and adds un-zoom before focus navigation (matching tmux behavior); both changes are well-scoped.

Sequence Diagram

sequenceDiagram
    participant Shell as Bash Shell
    participant Wrapper as Resources/bin/claude
    participant RealClaude as Real claude binary
    participant Hook as cmux claude-hook
    participant CLI as CLI/cmux.swift
    participant TM as TabManager
    participant WS as Workspace
    participant Surface as TerminalSurface

    Shell->>Wrapper: claude [args]
    Wrapper->>RealClaude: --help (capability check)
    RealClaude-->>Wrapper: help text
    Wrapper->>RealClaude: exec --session-id UUID --settings HOOKS_JSON [args]

    RealClaude->>Hook: session-start hook
    Hook->>CLI: subcommand=session-start
    CLI->>TM: resolveWorkspaceId (throws on failure)
    TM-->>CLI: workspaceId
    CLI-->>RealClaude: OK

    RealClaude->>Hook: notification hook
    Hook->>CLI: subcommand=notification
    CLI->>TM: resolveWorkspaceId (try?)
    alt TabManager live
        TM-->>CLI: workspaceId
        CLI->>Surface: isSurfaceLive check
        Surface-->>CLI: true/false
        CLI-->>RealClaude: OK (notify or skip)
    else TabManager torn down
        CLI-->>RealClaude: OK (graceful no-op)
    end

    RealClaude->>Hook: stop / session-end hook
    Hook->>CLI: subcommand=stop or session-end
    CLI->>TM: resolveWorkspaceId (try?)
    alt TabManager torn down
        CLI-->>RealClaude: OK (graceful no-op)
    else TabManager live
        CLI->>WS: setClaudeStatus Idle
        WS-->>CLI: done
        CLI-->>RealClaude: OK
    end
Loading

Comments Outside Diff (1)

  1. CLI/cmux.swift, line 18-25 (link)

    P2 Comment doesn't match the guard condition

    The inline comment reads:

    session-start is the only hook that truly needs a workspace

    but the guard condition also excludes "active" from graceful degradation, causing it to re-throw:

    if subcommand != "session-start" && subcommand != "active" {

    If active genuinely requires a live workspace, the comment should say so. If it doesn't, active should be included in the graceful-degradation path alongside the lifecycle hooks. Either way the comment and the code are in disagreement, which will confuse future maintainers.

Last reviewed commit: "Fix all Dependabot s..."

Comment thread Resources/shell-integration/cmux-bash-integration.bash
Comment thread Resources/bin/claude Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
CLI/cmux.swift (1)

10188-10222: ⚠️ Potential issue | 🟠 Major

Use sendV1Command(...) for notify_target here too.

Line 10214 still calls client.send(...), which does not turn plain-text ERROR: replies into throws. A teardown race after surface resolution will therefore print ERROR: ... instead of the intended OK.

🐛 Proposed fix
-            let response = (try? client.send(command: "notify_target \(workspaceId) \(surfaceId) \(payload)")) ?? "OK"
+            let response = (try? sendV1Command(
+                "notify_target \(workspaceId) \(surfaceId) \(payload)",
+                client: client
+            )) ?? "OK"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CLI/cmux.swift` around lines 10188 - 10222, The notify_target call uses
client.send(...) which doesn't turn plain-text "ERROR:" replies into throws;
replace the usage of client.send in the response assignment with the existing
sendV1Command(...) helper so ERROR responses become throws and the fallback "OK"
behavior on teardown still applies. Specifically, change the line that sets
response from (try? client.send(command: "notify_target \(workspaceId)
\(surfaceId) \(payload)")) ?? "OK" to use sendV1Command(...) (preserving the
same command string and the fallback to "OK"), leaving surrounding logic that
calls resolveSurfaceIdForClaudeHook, sessionStore.upsert, and setClaudeStatus
unchanged.
🧹 Nitpick comments (1)
Sources/Workspace.swift (1)

7987-7991: Centralize the post-unzoom reconciliation.

Line 7989 now routes through clearSplitZoom(), but the browser/portal/layout follow-up still lives only in toggleSplitZoom(panelId:). Pulling that common work into clearSplitZoom() will keep the two unzoom paths aligned.

♻️ Possible refactor
     func moveFocus(direction: NavigationDirection) {
         // If a pane is zoomed, un-zoom before navigating so the target
         // pane becomes visible — matches tmux behavior (`#1605`).
         if bonsplitController.isSplitZoomed {
-            _ = clearSplitZoom()
+            _ = clearSplitZoom(reason: "workspace.moveFocus")
         }
@@
     `@discardableResult`
-    func clearSplitZoom() -> Bool {
-        bonsplitController.clearPaneZoom()
+    func clearSplitZoom(reason: String = "workspace.clearSplitZoom") -> Bool {
+        guard bonsplitController.clearPaneZoom() else { return false }
+        reconcileTerminalPortalVisibilityForCurrentRenderedLayout()
+        reconcileBrowserPortalVisibilityForCurrentRenderedLayout(reason: reason)
+        beginEventDrivenLayoutFollowUp(reason: reason, includeGeometry: true)
+        return true
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/Workspace.swift` around lines 7987 - 7991, Move the post-unzoom
reconciliation (browser/portal/layout updates currently present only in
toggleSplitZoom(panelId:)) into clearSplitZoom() so both unzoom code paths
perform the same follow-up work; update clearSplitZoom() to perform the
browser/portal/layout reconciliation after clearing
bonsplitController.isSplitZoomed state and ensure toggleSplitZoom(panelId:)
calls clearSplitZoom() (removing its duplicated reconciliation code) so there is
a single, centralized unzoom behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@CLI/cmux.swift`:
- Around line 10037-10053: The current catch around
resolveWorkspaceIdForClaudeHook lets it fall back to workspace.current and thus
can pick a dead/unrelated workspace; change resolution to be strict/optional:
call resolveWorkspaceIdForClaudeHook in a mode that does NOT fall back to
workspace.current (e.g., add/pass allowFallback: false or use a new
resolveWorkspaceIdStrict helper) so it throws when the Claude workspace is gone,
and only treat missing workspace as non-fatal for subcommand values other than
"session-start" and "active"; if no workspace is resolved then skip downstream
calls that operate on the workspace (clear_notifications, setClaudeStatus,
resolveSurfaceIdForClaudeHook) and ensure session-end still proceeds to
sessionStore.consume when appropriate.

In `@Resources/shell-integration/cmux-bash-integration.bash`:
- Around line 102-106: The subshell invocations that end with ) & still create
parent-shell jobs; change fire-and-forget senders that call _cmux_send (and
similar helpers) to run an inner backgrounded command inside a foreground
subshell, e.g. ( command & ) >/dev/null 2>&1 to prevent the interactive shell
from tracking them, and for long-lived pollers (the poller/gits-status async
blocks) capture the PID with pid=$! immediately after starting and run disown
"$pid" so the job is removed from the shell job table; apply this pattern to all
similar blocks (the _cmux_send callers and the git-status async work) referenced
in the diff.

In `@Sources/AppDelegate.swift`:
- Around line 5357-5361: The code currently uses contextForMainWindow(window)
which can miss a stale ObjectIdentifier map and then falls back to
toggleSidebarInActiveMainWindow() even when a specific window was supplied;
replace that lookup with contextForMainTerminalWindow(window) (which can
reindex) and if it returns non-nil call sidebarState.toggle() and return true,
but do not fall back to toggleSidebarInActiveMainWindow() when the original
window parameter was non-nil—only call the active-window fallback when the
incoming window argument itself is nil.

---

Outside diff comments:
In `@CLI/cmux.swift`:
- Around line 10188-10222: The notify_target call uses client.send(...) which
doesn't turn plain-text "ERROR:" replies into throws; replace the usage of
client.send in the response assignment with the existing sendV1Command(...)
helper so ERROR responses become throws and the fallback "OK" behavior on
teardown still applies. Specifically, change the line that sets response from
(try? client.send(command: "notify_target \(workspaceId) \(surfaceId)
\(payload)")) ?? "OK" to use sendV1Command(...) (preserving the same command
string and the fallback to "OK"), leaving surrounding logic that calls
resolveSurfaceIdForClaudeHook, sessionStore.upsert, and setClaudeStatus
unchanged.

---

Nitpick comments:
In `@Sources/Workspace.swift`:
- Around line 7987-7991: Move the post-unzoom reconciliation
(browser/portal/layout updates currently present only in
toggleSplitZoom(panelId:)) into clearSplitZoom() so both unzoom code paths
perform the same follow-up work; update clearSplitZoom() to perform the
browser/portal/layout reconciliation after clearing
bonsplitController.isSplitZoomed state and ensure toggleSplitZoom(panelId:)
calls clearSplitZoom() (removing its duplicated reconciliation code) so there is
a single, centralized unzoom behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c7516144-c490-40f1-ae29-8e1649a8c7b7

📥 Commits

Reviewing files that changed from the base of the PR and between 8d4ba24 and 74b25f4.

⛔ Files ignored due to path filters (1)
  • web/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (10)
  • CLI/cmux.swift
  • Resources/bin/claude
  • Resources/shell-integration/cmux-bash-integration.bash
  • Sources/AppDelegate.swift
  • Sources/GhosttyTerminalView.swift
  • Sources/TabManager.swift
  • Sources/Update/UpdateTitlebarAccessory.swift
  • Sources/Workspace.swift
  • Sources/cmuxApp.swift
  • web/package.json

Comment thread CLI/cmux.swift
Comment thread Resources/shell-integration/cmux-bash-integration.bash Outdated
Comment thread Sources/AppDelegate.swift Outdated

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found across 11 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Resources/shell-integration/cmux-bash-integration.bash">

<violation number="1" location="Resources/shell-integration/cmux-bash-integration.bash:103">
P2: Incorrect Bash job-control assumption removed `disown`, which can reintroduce background job completion/kill notifications (prompt spam).</violation>
</file>

Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Add one-off context when rerunning by tagging @cubic-dev-ai with guidance or docs links (including llms.txt)
  • Ask questions if you need clarification on any suggestion

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread Resources/shell-integration/cmux-bash-integration.bash Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Sources/Workspace.swift`:
- Around line 8062-8067: clearSplitZoom currently clears pane zoom without
priming a browser portal host replacement, which lets a zoomed browser remain
attached to a stale host; update clearSplitZoom to call the same priming helper
used by toggleSplitZoom — preparePortalHostReplacementForNextDistinctClaim(...)
— before or immediately after bonsplitController.clearPaneZoom() (and before
reconcileBrowserPortalVisibilityForCurrentRenderedLayout) so the browser portal
host is replaced for the next distinct claim; reference clearSplitZoom,
bonsplitController.clearPaneZoom,
preparePortalHostReplacementForNextDistinctClaim, toggleSplitZoom, and
reconcileBrowserPortalVisibilityForCurrentRenderedLayout when applying the
change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9079db27-26cb-455d-b609-bdf1a1420d7c

📥 Commits

Reviewing files that changed from the base of the PR and between 0b8c60d and 4d53edd.

📒 Files selected for processing (4)
  • CLI/cmux.swift
  • Resources/bin/claude
  • Sources/AppDelegate.swift
  • Sources/Workspace.swift
✅ Files skipped from review due to trivial changes (1)
  • CLI/cmux.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • Resources/bin/claude

Comment thread Sources/Workspace.swift
… bash notifications

- Guard dangling ghostty_surface_t with isSurfaceLive before C API calls (#1558, #1608, #1767, #1815)
- Make CLI lifecycle hooks degrade gracefully when TabManager is torn down (#1775, #1715)
- Claude shim checks --session-id support before using it (#1818)
- Sidebar toggle targets its own window via WeakSelfBox pattern (#1779)
- Un-zoom pane before focus navigation, matching tmux behavior (#1605)
- Replace { } & disown with subshell ( ) & to suppress bash job notifications (#1565)
- Equalize splits uses proportional leaf counting for nested same-axis panes (#1622)
- Move Ghostty Settings below Settings in app menu (#1680)
Update next 16.1.6→16.1.7 (HTTP smuggling, CSRF bypass, DoS),
flatted 3.3.3→3.4.2 (prototype pollution), dompurify 3.3.1→3.3.3 (XSS),
minimatch 3.1.2→3.1.5 and 9.0.5→9.0.9 (ReDoS).
( ... ) & still creates a job in bash's job table — it is the & operator
that adds jobs, not the command type. Without disown, completed background
jobs produce "[N]+ Done" notifications at the next prompt. Use subshell
for process isolation + disown to suppress job-table tracking (#1565).
…ation

- Cache --session-id capability check per binary path+mtime to avoid
  re-running --help on every invocation (greptile P2)
- Fix comment to mention both session-start and active require a live
  workspace (greptile P2)
- Use sendV1Command instead of client.send for notify_target so ERROR
  replies become throws with proper fallback (coderabbit)
- Make workspace resolution strict: when a specific workspace ID was
  provided but the workspace is gone, throw instead of falling back
  to workspace.current which could target an unrelated workspace (coderabbit)
- Use contextForMainTerminalWindow (reindex-capable) for sidebar toggle;
  only fall back to active window when no target window provided (coderabbit)
- Centralize post-unzoom portal/layout reconciliation in clearSplitZoom()
  so moveFocus un-zoom path matches toggleSplitZoom behavior (coderabbit)
When un-zooming via clearSplitZoom (e.g. from moveFocus), capture the
zoomed pane's browser panel before clearing zoom and call
preparePortalHostReplacementForNextDistinctClaim so the browser portal
doesn't stay attached to the stale zoom host (coderabbit review).
@arzafran
arzafran force-pushed the fix/macos-issues-and-deps branch from e26b8ac to 2e8222a Compare March 20, 2026 11:58

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (4)
CLI/cmux.swift (3)

10139-10155: ⚠️ Potential issue | 🟠 Major

Don't short-circuit session-end before consuming the stored session.

Lines 10150-10152 put session-end in the silent early-return bucket, so when the workspace is already gone we print OK before reaching sessionStore.consume(...) on Lines 10332-10336. That leaves stale Claude hook state behind instead of cleaning it up. Missing workspace should suppress the later tab/surface mutations, but session-end still needs to consume the session first.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CLI/cmux.swift` around lines 10139 - 10155, The current early-return on
failed resolveWorkspaceIdForClaudeHook short-circuits "session-end" and prevents
sessionStore.consume(...) from running; update the conditional so "session-end"
is NOT included in the silent-return list (only exclude "session-start" and
"active"), allowing the session-end path to proceed to sessionStore.consume (see
sessionStore.consume usage) while ensuring subsequent tab/surface mutation code
checks for a valid fallbackWorkspaceId (or guards when workspace resolution
failed) before performing workspace mutations.

10227-10239: ⚠️ Potential issue | 🟠 Major

These best-effort guards can still notify the wrong surface.

The new try? / guard logic only no-ops if resolveSurfaceIdForClaudeHook(...) throws, but that helper ultimately relies on the permissive resolver on Lines 5907-5935. A stale ref/index surface handle still falls back to the workspace's focused surface, so stop/notification can land on an unrelated tab instead of quietly returning OK.

Also applies to: 10290-10298

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CLI/cmux.swift` around lines 10227 - 10239, The current best-effort block
uses resolveSurfaceIdForClaudeHook(...) which may silently fall back to the
workspace's focused surface and cause notifications to land on unrelated tabs;
change the logic so we only notify when the resolver returns a true match for
the original surface handle instead of a permissive fallback: either add/use a
strict resolver variant (e.g., resolveSurfaceIdForClaudeHookStrict) that returns
nil for stale handles, or have resolveSurfaceIdForClaudeHook expose whether the
result is a fallback and bail if so; apply this same guard where
sendV1Command("notify_target ...", client:) is called (both the shown block and
the duplicate at 10290-10298) so we never send a notification when the input
surfaceId cannot be resolved exactly.

10519-10530: ⚠️ Potential issue | 🟠 Major

resolveWorkspaceIdForClaudeHook is still not strict for every explicit target.

This helper still builds on resolveWorkspaceId(...), and that generic resolver on Lines 5874-5905 falls through to workspace.current for non-UUID/non-ref/non-index input. On top of that, the try? here turns stale ref/index misses into nil, which also falls through to workspace.current. So some bad explicit --workspace / CMUX_WORKSPACE_ID values will still target an unrelated live workspace and bypass the do/catch above.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CLI/cmux.swift` around lines 10519 - 10530, resolveWorkspaceIdForClaudeHook
currently lets explicit raw inputs fall back to
resolveWorkspaceId(nil,...)/workspace.current because it uses try? and the
generic resolveWorkspaceId accepts non-UUID/non-ref/non-index input; change it
so any explicit raw value is validated strictly: call resolveWorkspaceId(raw,
client: client) without try? (or add a strict flag to resolveWorkspaceId) and
propagate errors instead of swallowing them, only use client.sendV2 probe after
a successful resolution, and if resolution fails throw CLIError("Workspace no
longer available: \(raw)") (referencing resolveWorkspaceIdForClaudeHook,
resolveWorkspaceId, client.sendV2 and CLIError).
Resources/shell-integration/cmux-bash-integration.bash (1)

102-107: ⚠️ Potential issue | 🟠 Major

The fire-and-forget helpers are still parent-shell jobs.

Each of these blocks backgrounds the subshell itself, so Bash still creates a job in the interactive shell and only removes it afterward via disown. That can still leak job-control noise here, which is the exact behavior this change is trying to eliminate. For the one-shot senders, keep the subshell foregrounded and put the & on the inner _cmux_send instead.

Representative fix
-    (
-        _cmux_send "report_tty $_CMUX_TTY_NAME --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID"
-    ) >/dev/null 2>&1 &
-    disown 2>/dev/null
+    (
+        _cmux_send "report_tty $_CMUX_TTY_NAME --tab=$CMUX_TAB_ID --panel=$CMUX_PANEL_ID" >/dev/null 2>&1 &
+    )

Apply the same shape to the report_shell_state, ports_kick, and report_pwd blocks.

In interactive Bash with job control enabled, does `( command ) &` create a job in the parent shell's job table, and does `( command & )` avoid putting the inner command in the parent shell's job table? Please answer from the Bash manual sections on asynchronous commands and job control.

Also applies to: 118-121, 131-134, 411-415

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Resources/shell-integration/cmux-bash-integration.bash` around lines 102 -
107, The subshells like the one wrapping _cmux_send for report_tty (and the
similar report_shell_state, ports_kick, report_pwd blocks) are being
backgrounded with `( ... ) &` which creates a parent-shell job; instead,
background the inner _cmux_send so the subshell runs in the foreground and no
job is recorded: replace the pattern `( _cmux_send "..." ) >/dev/null 2>&1 &
disown` with `( _cmux_send "..." & ) >/dev/null 2>&1` (remove the disown) for
the report_tty, report_shell_state, ports_kick, and report_pwd blocks.
🧹 Nitpick comments (2)
Sources/TabManager.swift (1)

3345-3361: Add a regression test for the new axis-counting rule.

This helper now encodes a subtle invariant: same-axis descendants expand into their leaves, while perpendicular descendants collapse to one unit. A focused case for A | (B | C) and one perpendicular-child layout would make future tree-shape/orientation changes much safer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/TabManager.swift` around lines 3345 - 3361, Add a regression test
that exercises the countLeaves(in:along:) logic in TabManager by constructing
ExternalTreeNode trees for both same-axis nesting and perpendicular-child cases:
(1) a horizontal split A | (B | C) where countLeaves(in: , along: "horizontal")
should expand to 2 leaves for the nested same-axis child, and (2) a layout where
a split's child is perpendicular (e.g. A | (B — C) or similar) where
countLeaves(in: , along: "horizontal") should return 1 for the perpendicular
subtree; locate and call the private countLeaves(in:along:) helper (or test via
a public wrapper if needed) and assert the expected integer results using your
test framework so future changes to ExternalTreeNode/split orientation handling
are covered.
Sources/Workspace.swift (1)

8092-8101: Keep the unzoom exit sequence in one place.

toggleSplitZoom(panelId:) still has its own reconcile + portal-preparation flow, while clearSplitZoom(reason:) now owns the same sequence for other callers. Pulling the unzoom branch onto the same helper path would reduce the odds of these two flows drifting again.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/Workspace.swift` around lines 8092 - 8101, The unzoom exit sequence
currently duplicates logic: inside toggleSplitZoom(panelId:) it calls
reconcileTerminalPortalVisibilityForCurrentRenderedLayout(),
reconcileBrowserPortalVisibilityForCurrentRenderedLayout(reason:), and invokes
browserPanel(for:).preparePortalHostReplacementForNextDistinctClaim(...), while
clearSplitZoom(reason:) now centralizes this flow; refactor
toggleSplitZoom(panelId:) to call clearSplitZoom(reason:
"workspace.toggleSplitZoom") for the unzoom branch instead of repeating the
reconcile and portal-prep calls so all callers use the single helper, leaving
clearSplitZoom(reason:) responsible for calling
reconcileTerminalPortalVisibilityForCurrentRenderedLayout(),
reconcileBrowserPortalVisibilityForCurrentRenderedLayout(reason:), and
browserPanel(for:).preparePortalHostReplacementForNextDistinctClaim(inPane:paneId,
reason:).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@CLI/cmux.swift`:
- Around line 10139-10155: The current early-return on failed
resolveWorkspaceIdForClaudeHook short-circuits "session-end" and prevents
sessionStore.consume(...) from running; update the conditional so "session-end"
is NOT included in the silent-return list (only exclude "session-start" and
"active"), allowing the session-end path to proceed to sessionStore.consume (see
sessionStore.consume usage) while ensuring subsequent tab/surface mutation code
checks for a valid fallbackWorkspaceId (or guards when workspace resolution
failed) before performing workspace mutations.
- Around line 10227-10239: The current best-effort block uses
resolveSurfaceIdForClaudeHook(...) which may silently fall back to the
workspace's focused surface and cause notifications to land on unrelated tabs;
change the logic so we only notify when the resolver returns a true match for
the original surface handle instead of a permissive fallback: either add/use a
strict resolver variant (e.g., resolveSurfaceIdForClaudeHookStrict) that returns
nil for stale handles, or have resolveSurfaceIdForClaudeHook expose whether the
result is a fallback and bail if so; apply this same guard where
sendV1Command("notify_target ...", client:) is called (both the shown block and
the duplicate at 10290-10298) so we never send a notification when the input
surfaceId cannot be resolved exactly.
- Around line 10519-10530: resolveWorkspaceIdForClaudeHook currently lets
explicit raw inputs fall back to resolveWorkspaceId(nil,...)/workspace.current
because it uses try? and the generic resolveWorkspaceId accepts
non-UUID/non-ref/non-index input; change it so any explicit raw value is
validated strictly: call resolveWorkspaceId(raw, client: client) without try?
(or add a strict flag to resolveWorkspaceId) and propagate errors instead of
swallowing them, only use client.sendV2 probe after a successful resolution, and
if resolution fails throw CLIError("Workspace no longer available: \(raw)")
(referencing resolveWorkspaceIdForClaudeHook, resolveWorkspaceId, client.sendV2
and CLIError).

In `@Resources/shell-integration/cmux-bash-integration.bash`:
- Around line 102-107: The subshells like the one wrapping _cmux_send for
report_tty (and the similar report_shell_state, ports_kick, report_pwd blocks)
are being backgrounded with `( ... ) &` which creates a parent-shell job;
instead, background the inner _cmux_send so the subshell runs in the foreground
and no job is recorded: replace the pattern `( _cmux_send "..." ) >/dev/null
2>&1 & disown` with `( _cmux_send "..." & ) >/dev/null 2>&1` (remove the disown)
for the report_tty, report_shell_state, ports_kick, and report_pwd blocks.

---

Nitpick comments:
In `@Sources/TabManager.swift`:
- Around line 3345-3361: Add a regression test that exercises the
countLeaves(in:along:) logic in TabManager by constructing ExternalTreeNode
trees for both same-axis nesting and perpendicular-child cases: (1) a horizontal
split A | (B | C) where countLeaves(in: , along: "horizontal") should expand to
2 leaves for the nested same-axis child, and (2) a layout where a split's child
is perpendicular (e.g. A | (B — C) or similar) where countLeaves(in: , along:
"horizontal") should return 1 for the perpendicular subtree; locate and call the
private countLeaves(in:along:) helper (or test via a public wrapper if needed)
and assert the expected integer results using your test framework so future
changes to ExternalTreeNode/split orientation handling are covered.

In `@Sources/Workspace.swift`:
- Around line 8092-8101: The unzoom exit sequence currently duplicates logic:
inside toggleSplitZoom(panelId:) it calls
reconcileTerminalPortalVisibilityForCurrentRenderedLayout(),
reconcileBrowserPortalVisibilityForCurrentRenderedLayout(reason:), and invokes
browserPanel(for:).preparePortalHostReplacementForNextDistinctClaim(...), while
clearSplitZoom(reason:) now centralizes this flow; refactor
toggleSplitZoom(panelId:) to call clearSplitZoom(reason:
"workspace.toggleSplitZoom") for the unzoom branch instead of repeating the
reconcile and portal-prep calls so all callers use the single helper, leaving
clearSplitZoom(reason:) responsible for calling
reconcileTerminalPortalVisibilityForCurrentRenderedLayout(),
reconcileBrowserPortalVisibilityForCurrentRenderedLayout(reason:), and
browserPanel(for:).preparePortalHostReplacementForNextDistinctClaim(inPane:paneId,
reason:).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a5085b41-cbed-484f-9fb4-16467aa88b0a

📥 Commits

Reviewing files that changed from the base of the PR and between e26b8ac and 2e8222a.

⛔ Files ignored due to path filters (1)
  • web/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (10)
  • CLI/cmux.swift
  • Resources/bin/claude
  • Resources/shell-integration/cmux-bash-integration.bash
  • Sources/AppDelegate.swift
  • Sources/GhosttyTerminalView.swift
  • Sources/TabManager.swift
  • Sources/Update/UpdateTitlebarAccessory.swift
  • Sources/Workspace.swift
  • Sources/cmuxApp.swift
  • web/package.json
✅ Files skipped from review due to trivial changes (2)
  • web/package.json
  • Sources/AppDelegate.swift
🚧 Files skipped from review as they are similar to previous changes (2)
  • Sources/Update/UpdateTitlebarAccessory.swift
  • Resources/bin/claude

@arzafran arzafran closed this Jun 25, 2026
@arzafran
arzafran deleted the fix/macos-issues-and-deps branch June 25, 2026 15:40
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.

1 participant