Skip to content

Fix missing sidebar git branch metadata for workspaces - #2563

Merged
austinywang merged 6 commits into
mainfrom
issue-951-sidebar-branch-always-show
Apr 4, 2026
Merged

austinywang merged 6 commits into
mainfrom
issue-951-sidebar-branch-always-show

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Apr 3, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • backfill sidebar branch metadata from the workspace cwd when a workspace has not yet received a live git branch update
  • tighten the sidebar git metadata refresh path so missing or stale branch labels recover instead of staying blank
  • add regression coverage for the stale/missing sidebar branch case

Closes #951.

Testing

  • ./scripts/reload.sh --tag codex-dev-app

Notes

  • did not run local test suites

Summary by cubic

Fixes missing or blank git branch labels in the sidebar by backfilling from the workspace cwd, tracking known-good directories, and probing on workspace/panel create so stale labels recover quickly. Closes #951.

  • Bug Fixes
    • Backfill branch/PR from the workspace/panel cwd before OSC 7 reports; schedule initial probes on workspace open, new surfaces, and splits; skip initial probes for remote terminals.
    • Poll only panels whose current directory previously resolved git metadata; inherit cwd from the panel’s requested working directory; restore cleared or stale labels via periodic refresh.
    • Ensure background workspaces fetch branch without selection.
    • Fix clipboard integration by shimming a C-compatible read_clipboard_cb to bridge Bool/Void GhosttyKit importer variants and adding selection API compat wrappers to prevent runtime issues.

Written for commit fbe1a69. Summary will update on new commits.

Summary by CodeRabbit

  • Bug Fixes

    • Improved clipboard callback behavior and handling.
    • Enhanced copy-mode selection anchoring and cursor tracking.
  • Improvements

    • Refined Git branch and working-directory tracking across panels.
    • Improved Git metadata refresh scheduling, polling candidate selection, and background-probe behavior.
    • Initial Git refresh now runs for new terminal surfaces where applicable.
  • Tests

    • Added unit tests covering Git metadata probing, polling, and refresh behaviors.

@vercel

vercel Bot commented Apr 3, 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 Apr 4, 2026 0:54am

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Apr 3, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Refactors TabManager/Workspace git-metadata refresh scheduling to track probed directories per workspace+panel and schedule initial probes reliably for newly-created terminal surfaces. Adds C-ABI wrapper functions and refactors clipboard/read-clipboard callback handling in GhosttyTerminalView; adjusts bell-audio path access.

Changes

Cohort / File(s) Summary
Ghostty copy-mode & clipboard
Sources/GhosttyTerminalView.swift
Added two C-ABI wrapper functions for selection/clear APIs; replaced direct C calls with wrappers across copy-mode call sites. Extracted read_clipboard_cb logic into a fileprivate static helper returning Bool and wired via unsafeBitCast. Changed bellAudioPath() to read C-string pointer directly.
Git metadata tracking & scheduling
Sources/TabManager.swift
Introduced workspaceGitTrackedDirectoryByKey, changed candidate selection to include background panels only when directory matches tracked entry, replaced private scheduleInitialWorkspaceGitMetadataRefresh(...) with scheduleInitialWorkspaceGitMetadataRefreshIfPossible(...), added activeWorkspaceGitProbePanelIdsForTesting(...), updated probe application and cleanup to maintain tracked directory semantics.
Workspace integration
Sources/Workspace.swift
Now calls scheduleInitialWorkspaceGitMetadataRefreshIfPossible(...) after creating new splits/surfaces (reason: "splitCreate" / "surfaceCreate").
Tests
cmuxTests/TabManagerUnitTests.swift
Added unit tests for git-probe/poll behavior and background-surface branch inheritance; updated tests to use surface-level TabManager APIs.

Sequence Diagram

sequenceDiagram
    participant User
    participant Workspace
    participant TabManager
    participant GitProbe
    participant Surface

    User->>Workspace: create split / new surface
    activate Workspace
    Workspace->>Workspace: newTerminalSplit() / newTerminalSurface()
    Workspace->>TabManager: scheduleInitialWorkspaceGitMetadataRefreshIfPossible(reason)
    deactivate Workspace

    activate TabManager
    TabManager->>TabManager: check non-remote, resolve directory (gitProbeDirectory)
    TabManager->>TabManager: update workspaceGitTrackedDirectoryByKey
    TabManager->>GitProbe: schedule probe for directory (with reason)
    deactivate TabManager

    activate GitProbe
    GitProbe->>GitProbe: probe directory for branch/PR
    GitProbe->>Surface: publish branch/PR metadata
    deactivate GitProbe

    Surface->>TabManager: emit branch info update
    TabManager->>Surface: persist tracked state / notify UI
    User->>User: sidebar displays branch
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • #1570 — Modifies TabManager's initial git/PR probe scheduling and workspace restore/create flows; overlaps with probe/scheduling logic changes.
  • #792 — Adds Ghostty C APIs for selection/clear that this change wraps and integrates into copy-mode logic.
  • #2110 — Alters TabManager git-metadata polling/candidate selection logic and testing helpers; related to polling/refinement changes.

Poem

🐇
I hop through splits and surfaces bright,
Probing branches in the soft moonlight.
Clipboard wrapped and bells that sing,
Sidebar shows what probes will bring —
A happy rabbit, tracking Git tonight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.06% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: fixing missing sidebar git branch metadata for workspaces, which is the core objective of the PR.
Description check ✅ Passed The description covers what changed and why, but lacks explicit testing details and the demo video section is empty. However, it does reference testing via scripts and includes issue closure.
Linked Issues check ✅ Passed The PR changes directly address issue #951 by backfilling branch metadata from workspace cwd, scheduling initial git probes, tightening refresh logic, and adding test coverage to ensure branch labels display correctly.
Out of Scope Changes check ✅ Passed Changes include git branch metadata fixes, clipboard callback integration adjustments, and selection API compatibility wrappers—all directly supporting the core objective of fixing sidebar branch display without unrelated modifications.

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

✨ 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-951-sidebar-branch-always-show

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.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No issues found across 4 files

@greptile-apps

greptile-apps Bot commented Apr 3, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes sidebar git branch metadata being blank for workspaces that have not yet received a live git branch update (e.g., after session restore). It adds a fallback so that the focused panel is included as a polling candidate when workspace.gitBranch is already set but panelGitBranches is still empty, and ensures that clearSurfaceGitBranch schedules a re-probe so stale/missing labels recover automatically. Two behavioral regression tests cover the background-workspace inheritance and the stale-clear recovery paths.

Confidence Score: 5/5

Safe to merge — logic is correct, test coverage is solid, no regressions identified.

All findings are P2 or lower. The backfill fallback in trackedWorkspaceGitMetadataPollCandidatePanelIds is correctly guarded by workspace.gitBranch != nil, preventing spurious probes on fresh workspaces. The new scheduleWorkspaceGitMetadataRefreshIfPossible call in clearSurfaceGitBranch is de-duped by activeProbeKeys and only fires when there was something to clear. Two behavioral regression tests verify the exact bug scenarios through real git repos and observable state.

No files require special attention.

Important Files Changed

Filename Overview
Sources/TabManager.swift Core fix: clearSurfaceGitBranch now schedules a re-probe; gitProbeDirectory falls back to workspace.currentDirectory for the focused panel; trackedWorkspaceGitMetadataPollCandidatePanelIds adds the focused panel as a fallback candidate when workspace.gitBranch is set but panelGitBranches is empty.
Sources/Workspace.swift Supporting data model; panelGitBranches, updatePanelGitBranch, clearPanelGitBranch, and fallbackBranch plumbing are all consistent with the new polling logic.
Sources/GhosttyTerminalView.swift Large file listed as changed; no git-branch specific logic found in this file — changes appear to be minor or incidental to the surrounding refactor.
cmuxTests/TabManagerUnitTests.swift Two new behavioral regression tests: one verifies background-workspace git branch inheritance via workspace.currentDirectory, the other verifies that clearSurfaceGitBranch + periodic refresh restores the branch. Both test through real git repos and observable runtime state.

Sequence Diagram

sequenceDiagram
    participant TM as TabManager
    participant WS as Workspace
    participant GP as GitProbe

    Note over TM,WS: Session restore / new background workspace
    TM->>WS: workspace.gitBranch set (from snapshot)
    WS-->>TM: panelGitBranches still empty
    TM->>TM: trackedWorkspaceGitMetadataPollCandidatePanelIds()
    Note right of TM: candidatePanelIds empty →<br/>fallback: gitBranch != nil &&<br/>gitProbeDirectory != nil →<br/>insert focusedPanelId
    TM->>GP: scheduleWorkspaceGitMetadataRefreshIfPossible()
    GP-->>TM: git branch result
    TM->>WS: updatePanelGitBranch(panelId, "main")
    WS->>WS: panelGitBranches[panelId] = "main"
    WS->>WS: gitBranch = "main" (focused panel)

    Note over TM,WS: clearSurfaceGitBranch (stale/reset)
    TM->>WS: clearPanelGitBranch(panelId)
    WS->>WS: panelGitBranches[panelId] = nil
    WS->>WS: gitBranch = nil
    TM->>GP: scheduleWorkspaceGitMetadataRefreshIfPossible() NEW
    Note right of TM: workspaceGitTrackedDirectoryByKey<br/>still set → panel is candidate
    GP-->>TM: git branch result
    TM->>WS: updatePanelGitBranch(panelId, "main") restored
Loading

Reviews (1): Last reviewed commit: "Tighten sidebar git metadata polling" | Re-trigger Greptile

@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

🧹 Nitpick comments (1)
cmuxTests/TabManagerUnitTests.swift (1)

628-632: Consider adding a comment explaining the 12-second timeout.

The 12-second timeout is notably longer than the default 3-second timeout used elsewhere in the test suite. If this extended duration is intentional (e.g., waiting for initial workspace probing to settle before testing the remote split behavior), a brief comment would clarify this for future maintainers.

📝 Suggested clarification
+        // Wait for any initial git probe triggered by workspace creation to complete
+        // before testing that remote splits don't schedule additional probes.
         XCTAssertTrue(
             waitForCondition(timeout: 12.0) {
                 manager.activeWorkspaceGitProbePanelIdsForTesting(workspaceId: workspace.id).isEmpty
             }
         )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxTests/TabManagerUnitTests.swift` around lines 628 - 632, Add a brief
inline comment above the waitForCondition call that uses a 12.0 second timeout
explaining why this test needs a longer wait than the usual 3s (e.g., to allow
initial workspace probing/remote split propagation to settle before asserting
manager.activeWorkspaceGitProbePanelIdsForTesting(workspaceId:) is empty); keep
the comment short and reference the intent (initial probing/remote split) so
future maintainers understand why waitForCondition(timeout: 12.0) is larger than
the suite default.
🤖 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/GhosttyTerminalView.swift`:
- Around line 1155-1159: The closure assigned to runtimeConfig.read_clipboard_cb
captures callbackContext.runtimeSurface before the DispatchQueue.main.async hop,
risking a data race because TerminalSurface.surface is created/cleared on the
main thread; move the access to callbackContext.runtimeSurface (and any use of
TerminalSurface.surface/native pointer passed to
ghostty_surface_complete_clipboard_request) inside the DispatchQueue.main.async
block so the runtimeSurface is read and used only on the main thread (update the
closure in runtimeConfig.read_clipboard_cb to call
GhosttyApp.callbackContext(from: userdata) before dispatching but obtain and use
callbackContext.runtimeSurface only within the DispatchQueue.main.async
closure).

In `@Sources/TabManager.swift`:
- Around line 1065-1071: The gitProbeDirectory logic short-circuits when
panelDirectories[panelId] or terminalPanel(for:).requestedWorkingDirectory is an
empty/whitespace string; change gitProbeDirectory to evaluate each candidate for
non-empty (trim whitespace) before coalescing so the fallback chain can
continue. Specifically, inspect the candidates in order
(workspace.panelDirectories[panelId], workspace.terminalPanel(for:
panelId)?.requestedWorkingDirectory, then workspace.focusedPanelId == panelId ?
workspace.currentDirectory) and only pass the first non-empty trimmed String
into normalizedWorkingDirectory (or skip to the next candidate if nil/empty),
ensuring empty/whitespace values don’t block falling back.

In `@Sources/Workspace.swift`:
- Around line 7644-7648: The code schedules initial git metadata refresh only
when terminals are created via newTerminalSplit(...) or newTerminalSurface(...),
but splitTabBar(_:didSplitPane:) and other direct TerminalPanel constructions
(including placeholder repair paths referenced around the other occurrence)
still instantiate TerminalPanel directly and miss the scheduling call; update
those direct construction sites to call
owningTabManager?.scheduleInitialWorkspaceGitMetadataRefreshIfPossible(workspaceId:
id, panelId: newPanel.id, reason: "splitCreate") immediately after creating the
TerminalPanel (or better, refactor creation to a single helper/factory used by
newTerminalSplit/newTerminalSurface and splitTabBar(_:didSplitPane:) so all
paths consistently invoke the scheduling), and ensure the placeholder-repair
path uses the same helper so Bonsplit-created terminals receive the initial git
backfill.

---

Nitpick comments:
In `@cmuxTests/TabManagerUnitTests.swift`:
- Around line 628-632: Add a brief inline comment above the waitForCondition
call that uses a 12.0 second timeout explaining why this test needs a longer
wait than the usual 3s (e.g., to allow initial workspace probing/remote split
propagation to settle before asserting
manager.activeWorkspaceGitProbePanelIdsForTesting(workspaceId:) is empty); keep
the comment short and reference the intent (initial probing/remote split) so
future maintainers understand why waitForCondition(timeout: 12.0) is larger than
the suite default.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 222b3635-8cb5-4421-922d-cb610b8641aa

📥 Commits

Reviewing files that changed from the base of the PR and between 552747a and 292548a.

📒 Files selected for processing (4)
  • Sources/GhosttyTerminalView.swift
  • Sources/TabManager.swift
  • Sources/Workspace.swift
  • cmuxTests/TabManagerUnitTests.swift

Comment thread Sources/GhosttyTerminalView.swift Outdated
Comment thread Sources/TabManager.swift
Comment on lines 1065 to 1071
private func gitProbeDirectory(for workspace: Workspace, panelId: UUID) -> String? {
// Match the sidebar directory fallback chain so hidden/background panels can
// still probe git metadata before OSC 7 has reported a live cwd.
let rawDirectory = workspace.panelDirectories[panelId]
?? workspace.terminalPanel(for: panelId)?.requestedWorkingDirectory
?? (workspace.focusedPanelId == panelId ? workspace.currentDirectory : nil)
return rawDirectory.flatMap(normalizedWorkingDirectory)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Blank directory strings still short-circuit the probe fallback chain.

panelDirectories[panelId] and requestedWorkingDirectory are coalesced before they're normalized. If either value is "" or whitespace, the ?? chain stops there, normalizedWorkingDirectory returns nil, and the initial/backfill probe never falls through to the next source. That can still leave the sidebar branch blank in the startup race this PR is trying to fix. Sources/Workspace.swift:7558-7567 already uses the non-empty check before picking requestedWorkingDirectory.

💡 Suggested fix
     private func gitProbeDirectory(for workspace: Workspace, panelId: UUID) -> String? {
         // Match the sidebar directory fallback chain so hidden/background panels can
         // still probe git metadata before OSC 7 has reported a live cwd.
-        let rawDirectory = workspace.panelDirectories[panelId]
-            ?? workspace.terminalPanel(for: panelId)?.requestedWorkingDirectory
-            ?? (workspace.focusedPanelId == panelId ? workspace.currentDirectory : nil)
-        return rawDirectory.flatMap(normalizedWorkingDirectory)
+        let panelDirectory = normalizedWorkingDirectory(workspace.panelDirectories[panelId])
+        let requestedDirectory = normalizedWorkingDirectory(
+            workspace.terminalPanel(for: panelId)?.requestedWorkingDirectory
+        )
+        let focusedDirectory = workspace.focusedPanelId == panelId
+            ? normalizedWorkingDirectory(workspace.currentDirectory)
+            : nil
+        return panelDirectory ?? requestedDirectory ?? focusedDirectory
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/TabManager.swift` around lines 1065 - 1071, The gitProbeDirectory
logic short-circuits when panelDirectories[panelId] or
terminalPanel(for:).requestedWorkingDirectory is an empty/whitespace string;
change gitProbeDirectory to evaluate each candidate for non-empty (trim
whitespace) before coalescing so the fallback chain can continue. Specifically,
inspect the candidates in order (workspace.panelDirectories[panelId],
workspace.terminalPanel(for: panelId)?.requestedWorkingDirectory, then
workspace.focusedPanelId == panelId ? workspace.currentDirectory) and only pass
the first non-empty trimmed String into normalizedWorkingDirectory (or skip to
the next candidate if nil/empty), ensuring empty/whitespace values don’t block
falling back.

Comment thread Sources/Workspace.swift
Comment on lines +7644 to +7648
owningTabManager?.scheduleInitialWorkspaceGitMetadataRefreshIfPossible(
workspaceId: id,
panelId: newPanel.id,
reason: "splitCreate"
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

This still misses Bonsplit-created terminals.

These hooks only run when terminal creation goes through newTerminalSplit(...) or newTerminalSurface(...). splitTabBar(_:didSplitPane:) still constructs TerminalPanel directly for UI splits and placeholder repair, so those tabs can still come up with no initial git backfill and keep a blank branch label. Please route those direct creation sites through the same scheduling path as well.

Also applies to: 7725-7729

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

In `@Sources/Workspace.swift` around lines 7644 - 7648, The code schedules initial
git metadata refresh only when terminals are created via newTerminalSplit(...)
or newTerminalSurface(...), but splitTabBar(_:didSplitPane:) and other direct
TerminalPanel constructions (including placeholder repair paths referenced
around the other occurrence) still instantiate TerminalPanel directly and miss
the scheduling call; update those direct construction sites to call
owningTabManager?.scheduleInitialWorkspaceGitMetadataRefreshIfPossible(workspaceId:
id, panelId: newPanel.id, reason: "splitCreate") immediately after creating the
TerminalPanel (or better, refactor creation to a single helper/factory used by
newTerminalSplit/newTerminalSurface and splitTabBar(_:didSplitPane:) so all
paths consistently invoke the scheduling), and ensure the placeholder-repair
path uses the same helper so Bonsplit-created terminals receive the initial git
backfill.

@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

♻️ Duplicate comments (1)
Sources/GhosttyTerminalView.swift (1)

991-998: ⚠️ Potential issue | 🔴 Critical

Move the runtimeSurface lookup back onto the main queue.

Line 992 still captures callbackContext.runtimeSurface before the DispatchQueue.main.async hop. TerminalSurface.surface is created and cleared on the main thread in this file, so this reintroduces the stale-pointer race; the later equality check is too late because requestSurface is already captured.

Suggested fix
 fileprivate static func runtimeReadClipboardCallback(
     _ userdata: UnsafeMutableRawPointer?,
     _ location: ghostty_clipboard_e,
     _ state: UnsafeMutableRawPointer?
 ) -> Bool {
-    guard let callbackContext = Self.callbackContext(from: userdata),
-          let requestSurface = callbackContext.runtimeSurface else { return false }
+    guard let callbackContext = Self.callbackContext(from: userdata) else { return false }

     DispatchQueue.main.async {
+        guard let requestSurface = callbackContext.runtimeSurface else { return }
         func completeClipboardRequest(with text: String) {
             let finish = {
                 guard callbackContext.runtimeSurface == requestSurface else { return }
                 text.withCString { ptr in
                     ghostty_surface_complete_clipboard_request(requestSurface, ptr, state, false)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/GhosttyTerminalView.swift` around lines 991 - 998, The code captures
callbackContext.runtimeSurface into requestSurface before hopping to
DispatchQueue.main.async, which can race because TerminalSurface.surface is only
created/cleared on the main thread; instead, only capture callbackContext (via
Self.callbackContext(from: userdata)) before the async and move the
runtimeSurface lookup/guard (i.e., obtain requestSurface =
callbackContext.runtimeSurface and the equality check that currently compares
callbackContext.runtimeSurface == requestSurface) inside the
DispatchQueue.main.async block (for example at the start of
completeClipboardRequest or immediately upon entering the main block) so the
surface is read on the main thread and the stale-pointer race is eliminated
while preserving the later equality guard.
🤖 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/GhosttyTerminalView.swift`:
- Around line 14-18: The code binds to undocumented internal symbols
ghostty_surface_clear_selection and ghostty_surface_select_cursor_cell via
`@_silgen_name` wrappers (ghostty_surface_clear_selection_compat,
ghostty_surface_select_cursor_cell_compat); remove these private-symbol bindings
and any calls to those compatibility functions, and replace the selection logic
with the documented public embedding API (use the Ghostty input/mouse event
handling and rendering callbacks or the documented configuration options) or
else request/implement a formal public C shim in Ghostty and call that
instead—search for usages of ghostty_surface_clear_selection_compat and
ghostty_surface_select_cursor_cell_compat to remove/replace them.

---

Duplicate comments:
In `@Sources/GhosttyTerminalView.swift`:
- Around line 991-998: The code captures callbackContext.runtimeSurface into
requestSurface before hopping to DispatchQueue.main.async, which can race
because TerminalSurface.surface is only created/cleared on the main thread;
instead, only capture callbackContext (via Self.callbackContext(from: userdata))
before the async and move the runtimeSurface lookup/guard (i.e., obtain
requestSurface = callbackContext.runtimeSurface and the equality check that
currently compares callbackContext.runtimeSurface == requestSurface) inside the
DispatchQueue.main.async block (for example at the start of
completeClipboardRequest or immediately upon entering the main block) so the
surface is read on the main thread and the stale-pointer race is eliminated
while preserving the later equality guard.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7973b5cc-56dd-4001-ba10-59e7cb8080be

📥 Commits

Reviewing files that changed from the base of the PR and between 292548a and fbe1a69.

📒 Files selected for processing (1)
  • Sources/GhosttyTerminalView.swift

Comment on lines +14 to +18
@_silgen_name("ghostty_surface_clear_selection")
private func ghostty_surface_clear_selection_compat(_ surface: ghostty_surface_t) -> Bool

@_silgen_name("ghostty_surface_select_cursor_cell")
private func ghostty_surface_select_cursor_cell_compat(_ surface: ghostty_surface_t) -> Bool

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== public headers / interfaces mentioning Ghostty selection APIs =="
fd -HI 'ghostty\.h$|\.swiftinterface$|module\.modulemap$' \
  | xargs -r rg -n 'ghostty_surface_(clear_selection|select_cursor_cell|has_selection|read_selection|read_text|free_text)'

echo
echo "== vendored GhosttyKit / libghostty artifacts containing the private symbols =="
fd -HI 'GhosttyKit|libghostty|ghostty' \
  | xargs -r rg -a -n 'ghostty_surface_(clear_selection|select_cursor_cell)'

Repository: manaflow-ai/cmux

Length of output: 1798


🌐 Web query:

ghostty_surface_clear_selection ghostty_surface_select_cursor_cell public API

💡 Result:

The functions ghostty_surface_clear_selection and ghostty_surface_select_cursor_cell appear to be internal methods in Ghostty's Surface.zig implementation for managing terminal text selections, but they are not part of the documented public libghostty C API. The public embedding API (documented at ghostty-org-ghostty.mintlify.app) provides input handling (e.g., ghostty_surface_mouse_button, ghostty_surface_mouse_pos), rendering (ghostty_surface_draw), and core types like ghostty_surface_t, but no explicit public functions for direct selection manipulation like clearing or selecting the cursor cell. Ghostty's libghostty C API is explicitly noted as unstable and primarily used internally by the macOS app, with full documentation in Zig source files rather than comprehensive C headers. Selection management is handled internally via mouse events and config options like selection-clear-on-typing (from ghostty.org/docs/config/reference), with structures like ghostty_selection_s defined in include/ghostty.h for coordinates (top_left, bottom_right, rectangle flag). For embedding, selections are typically managed through mouse input callbacks rather than direct API calls. libghostty-vt (separate VT library) focuses on parsing/rendering but lacks surface-level selection APIs. No official public API documentation confirms these exact functions as exposed.

Citations:


🌐 Web query:

Ghostty C API header ghostty.h public interface selection functions

💡 Result:

The ghostty.h header file defines the public C API for embedding Ghostty's terminal emulator (libghostty), primarily used by the macOS app but designed for general embedding. It includes opaque types (ghostty_app_t, ghostty_surface_t, etc.), enums for platforms, clipboards, input states, modifiers (GHOSTTY_MODS_), key codes (GHOSTTY_KEY_ based on W3C UI Events), mouse buttons, actions, and structs for input events (ghostty_input_key_s), clipboard content, surface configs, etc. No explicit "selection functions" (e.g., for text selection like get_selection, select_area) are visible in the partial header snippets. The API focuses on initialization (ghostty_init, ghostty_app_new, ghostty_surface_new), input handling (ghostty_surface_key, ghostty_surface_mouse_button, etc.), configuration, and rendering callbacks. Clipboard support includes GHOSTTY_CLIPBOARD_STANDARD and GHOSTTY_CLIPBOARD_SELECTION enums, suggesting selection clipboard integration, but specific functions like paste_from_clipboard or copy_to_selection are not shown in available sources. Full function declarations (e.g., ghostty_surface_get_selection) are not in the extracted snippets; documentation notes the API is unstable and primarily documented in Zig source. For complete details, view the full header at https://github.com/ghostty-org/ghostty/blob/main/include/ghostty.h. A separate stable libghostty-vt API (ghostty/vt.h) exists for VT parsing/terminal state without rendering/GUI.

Citations:


Remove dependency on undocumented Ghostty internal ABI symbols.

ghostty_surface_clear_selection and ghostty_surface_select_cursor_cell are not part of the documented public libghostty C API. Web search of official Ghostty documentation confirms these are internal Surface.zig implementation methods, not exposed public functions. The public embedding API (documented at ghostty-org-ghostty.mintlify.app) focuses on input handling and rendering callbacks; selection management is handled internally via mouse events and configuration options, not direct API calls. Binding to these private symbols via _silgen_name creates a fragile dependency that may break when Ghostty updates.

Either request Ghostty to stabilize and document these symbols as part of the public ABI, or redesign the selection logic to use the documented public input/event APIs instead.

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

In `@Sources/GhosttyTerminalView.swift` around lines 14 - 18, The code binds to
undocumented internal symbols ghostty_surface_clear_selection and
ghostty_surface_select_cursor_cell via `@_silgen_name` wrappers
(ghostty_surface_clear_selection_compat,
ghostty_surface_select_cursor_cell_compat); remove these private-symbol bindings
and any calls to those compatibility functions, and replace the selection logic
with the documented public embedding API (use the Ghostty input/mouse event
handling and rendering callbacks or the documented configuration options) or
else request/implement a formal public C shim in Ghostty and call that
instead—search for usages of ghostty_surface_clear_selection_compat and
ghostty_surface_select_cursor_cell_compat to remove/replace them.

@austinywang
austinywang merged commit c1b8a5c into main Apr 4, 2026
18 checks passed
Jesssullivan added a commit to Jesssullivan/cmux that referenced this pull request Apr 5, 2026
* fix: fall back to focused surface when new-split target is stale

When `new-split` receives a surface UUID that no longer exists (e.g. a
closed teammate pane), fall back to the focused surface instead of
returning a "Surface not found" error. This matches `new-pane` behavior
and fixes agent spawning after team shutdown in Claude Code.

* Add editable workspace descriptions (manaflow-ai#2475)

* Add editable workspace descriptions

* Add workspace description focus UI tests

* Include workspace description UI tests in project

* Fix workspace description palette focus

* Stabilize workspace description UI tests

* Force socket mode in description UI tests

* Use tagged socket path in description UI tests

* Fix workspace description UI test socket setup

* Start control socket for UI test launches

* Use socket env overrides in description UI tests

* Rewrite description UI tests without socket access

* Verify saved description by reopening editor

* Add workspace description focus debug logs

* Prevent terminal focus restore during command palette

* Trace workspace description shift-enter handling

* Add failing Shift-Enter description UI test

* Fix Shift-Enter in workspace description editor

* Use live multiline editor state for description submit

* Log submitted and normalized workspace descriptions

* Trace lower-level Shift-Enter editor routing

* Add failing sidebar markdown line-break regression test

* Preserve workspace description line breaks in sidebar

* Add sidebar multiline description UI smoke test

* Expose multiline sidebar descriptions to accessibility

* Fix sidebar description refresh lag

* Add failing workspace description whitespace test

* Preserve workspace description whitespace

* fix: keep multiline palette navigation in editor

* fix: cap workspace description editor growth

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* Detect listening ports for remote SSH workspaces (manaflow-ai#2398)

* Add failing SSH remote port detection regression

* Detect listening ports for remote SSH workspaces

* Retry remote SSH TTY reporting until the target surface exists

* Address relay RPC review comments

* Avoid host-wide remote port leaks for cmux ssh

* Make remote port scans prompt-aware

* Fall back when remote ss omits pid data

* Tighten remote port scan handoff

* Keep raw RPC output intact

* Clean up relay TTY handoff review follow-up

* Fix remote ssh port surfacing

* Fix cmux ssh bootstrap and orphan cleanup

* Address SSH remote review feedback

* Stabilize SSH remote metadata regression

* Fix shell timing and suppressed focus recovery

* Harden suppressed focus recovery in tests

* Stabilize focus recovery regression timing

* Fix SSH relay auth and bootstrap handoff

* Fix SSH metadata cleanup assertion

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* Add SSH foreground-auth regression tests

* Defer remote bootstrap until SSH auth succeeds

* Fix session restore terminal cursor focus race (manaflow-ai#2471)

* Fix session restore terminal cursor focus race

* Fix terminal ready focus observer key

* wip

* Revert accidental submodule pointer changes from wip commit

The wip commit bumped ghostty and bonsplit pointers alongside the
actual code fix. Revert them to match main so the PR only contains
the session cursor race fix.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* Support chorded keyboard shortcuts (manaflow-ai#2528)

* Support chorded keyboard shortcuts

* Fix escape handling in shortcut recorder

* Add settings.json shortcut overrides

* Add regression test for chord reset on deactivate

* Fix shortcut chord cleanup edge cases

* Add regression tests for chord prefix edge cases

* Fix configurable chord prefix routing

* Add CLI reload-config command

* Simplify reload-config CLI command

* Add regression tests for shortcut reload edges

* Fix shortcut reload and chord edge cases

* Add managed settings.json defaults and schema docs

* fix: harden shortcut routing edge cases

* fix: preserve palette and browser shortcut routing

* chore: retrigger missing PR workflows

* feat: add settings.json entry points

* feat: make workspace color palette a named dictionary

* feat: add textedit button for settings file

* refactor: reorder ghostty menu items

* refactor: restore settings menu ordering

* refactor: restyle settings file entry

* refactor: move settings file actions into header

* refactor: simplify settings file header action

* docs: explain shortcut chords from settings

* refactor: reorder shortcut chord actions

* fix: notify after swapping settings file store

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

* Find Homebrew go for dev remote bootstrap

* Fix missing sidebar git branch metadata for workspaces (manaflow-ai#2563)

* Fix sidebar workspace branch backfill

* Add stale branch sidebar regression

* Patch GhosttyKit header compatibility

* Match Ghostty clipboard callback signature

* Tighten sidebar git metadata polling

* Fix Ghostty clipboard callback bridge

---------

Co-authored-by: austinpower1258 <austinwang115@gmail.com>

* Reuse SSH control master for remote relay

* fix: harden deferred ssh reconnect handling

* Fix missing sidebar ports for agent-run dev servers (manaflow-ai#2562)

* test: cover agent-owned sidebar ports

* fix: track agent dev-server ports in sidebar

* fix: harden ssh localcommand escaping

* Fix sidebar layout loop and CLI socket deadlocks (manaflow-ai#2601)

* Fix sidebar layout loop and CLI socket deadlocks

* Fix sidebar socket target validation

* Move sidebar tab resolution onto main queue

* Fix sidebar mutation closure parameters

* fix: add missing CryptoKit import from upstream merge

---------

Co-authored-by: Anusheel Bhushan <anusheel@gmail.com>
Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com>
Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
Co-authored-by: Austin Wang <austinwang115@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview — fbe1a698 Deployed Apr 4, 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.

Sidebar sometimes fails to show git branch for a workspace

2 participants