Skip to content

Refresh background workspace git metadata after external checkout#987

Merged
austinywang merged 5 commits intomainfrom
issue-915-terminal-not-loaded
Mar 6, 2026
Merged

Refresh background workspace git metadata after external checkout#987
austinywang merged 5 commits intomainfrom
issue-915-terminal-not-loaded

Conversation

@austinywang
Copy link
Copy Markdown
Contributor

@austinywang austinywang commented Mar 6, 2026

Summary

  • add a bounded app-side git metadata refresh window for workspaces created with an explicit cwd
  • update background workspace branch state even when the repo changes externally before the shell emits another prompt
  • clear stale PR metadata when the seeded branch changes and add an end-to-end regression for the external checkout case

Verification

  • built and launched with ./scripts/reload.sh --tag issue-915-terminal-not-loaded
  • did not run tests locally (repo policy)

Summary by cubic

Fixes background workspaces not updating git branch/status after external repo changes so the sidebar stays correct. Addresses Linear issue 915 by eager-loading background terminals and refreshing git metadata without waiting for a new shell prompt.

  • Bug Fixes
    • Adds a staged, bounded git probe for workspaces created with an explicit cwd; updates branch and dirty state.
    • Updates background workspace branch and clears stale PR metadata when the repo changes externally before the next prompt.
    • Eager-loads terminal surfaces for non-focused workspaces and completes background priming with a 2s timeout.
    • Adds an end-to-end regression test verifying branch refresh after external checkout while preserving the selected workspace.

Written for commit 0285cd1. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Per-tab background pre-loading so workspaces and terminals can prime without interrupting your current workflow
    • Automatic Git branch detection and metadata refresh for newly added or background-loaded workspaces
    • Faster terminal availability via background terminal surface initialization
  • Tests

    • Added a regression test verifying background workspaces refresh Git branch state after external checkouts

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 6, 2026

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

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Mar 6, 2026 7:10am

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 6, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Adds internal background priming for tabs/workspaces: per-tab priming state and policy, background terminal surface startup, Git metadata probe workflow with delayed retries, pending-background-load bookkeeping, UI wiring to trigger priming, and small public API additions to opt into eager terminal loading.

Changes

Cohort / File(s) Summary
ContentView / UI priming
Sources/ContentView.swift
Wires per-tab shouldPrimeInBackground into each tab view; adds SwiftUI task to run primeBackgroundWorkspaceIfNeeded(workspaceId:) and integrates priming state machine and orchestration.
TabManager: background loads & Git probes
Sources/TabManager.swift
Adds pendingBackgroundWorkspaceLoadIds, request/complete/prune API for background loads; implements initial Git metadata probe workflow with delayed probes, generation/timer tracking, snapshot application, and lifecycle cleanup.
TerminalController callsites
Sources/TerminalController.swift
Updates call sites to forward new eagerLoadTerminal: Bool parameter to TabManager.addWorkspace / addTab.
Workspace helpers
Sources/Workspace.swift
Adds requestBackgroundTerminalSurfaceStartIfNeeded() and hasLoadedTerminalSurface() to coordinate background terminal surface startup and readiness checks.
Tests
tests_v2/test_cli_new_workspace_external_git_branch_refresh.py
New regression test to verify background workspace Git branch refresh after external repo checkout without switching active workspace; includes CLI helpers and polling logic.

Sequence Diagram

sequenceDiagram
    participant User
    participant TabManager
    participant ContentView
    participant Workspace
    participant TerminalPanel

    User->>TabManager: addWorkspace(..., eagerLoadTerminal: true)
    TabManager->>TabManager: requestBackgroundWorkspaceLoad(workspaceId)
    TabManager->>TabManager: scheduleInitialWorkspaceGitMetadataRefresh(workspaceId)
    TabManager-->>ContentView: publish pendingBackgroundWorkspaceLoadIds
    ContentView->>ContentView: detect workspace in pending set
    ContentView->>ContentView: primeBackgroundWorkspaceIfNeeded(workspaceId)
    ContentView->>Workspace: requestBackgroundTerminalSurfaceStartIfNeeded()
    Workspace->>TerminalPanel: surface.requestBackgroundSurfaceStartIfNeeded()
    TerminalPanel-->>TerminalPanel: initialize surface (async)
    TerminalPanel-->>Workspace: surface ready
    ContentView->>TabManager: completeBackgroundWorkspaceLoad(workspaceId)
    TabManager->>TabManager: remove from pendingBackgroundWorkspaceLoadIds
    TabManager-->>ContentView: publish updated pending set
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I hopped through tabs while they slept in the stack,

I nudged Git branches awake at the back.
Surfaces bloom where the workspaces lay,
Quietly primed for the next busy day.

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main change: refreshing git metadata for background workspaces after external checkouts.
Description check ✅ Passed The description covers the Summary and Verification sections from the template, though Testing details and checklist items are incomplete or missing.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch issue-915-terminal-not-loaded

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

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 6, 2026

Greptile Summary

This PR adds a bounded background git-metadata refresh pipeline for workspaces that are opened with an explicit cwd. When such a workspace is created, up to six probes fire at [0, 0.5, 1.5, 3.0, 6.0, 10.0] seconds on a utility background queue, each running git branch --show-current + git status --porcelain -uno and pushing the results back to the main actor. This lets the sidebar reflect external git checkout operations even before the shell emits a prompt. A companion SwiftUI polling loop (50 ms, 2 s cap) ensures the background workspace's terminal surface is primed before the workspace becomes interactive.

Key changes:

  • TabManager: new pendingBackgroundWorkspaceLoadIds published set + multi-probe scheduling with generation-based cancellation guard
  • ContentView: .task(id:) modifier drives the surface-priming loop; pendingBackgroundWorkspaceLoadIds items are now included in the mount-pinned set so background workspaces aren't evicted
  • TerminalController: call-site simplification via new eagerLoadTerminal parameter
  • Workspace: two new helpers (requestBackgroundTerminalSurfaceStartIfNeeded, hasLoadedTerminalSurface)
  • New e2e regression test covering the external-checkout scenario

The main technical concern is in runGitCommand: stdout is read after waitUntilExit(), which is the classic macOS pipe-buffer deadlock pattern — if git status output ever exceeds ~64 KB the background thread will hang. All six probes are also dispatched unconditionally with no pre-subprocess cancellation check, meaning closed workspaces still trigger up to five extra git forks.

Confidence Score: 3/5

  • Safe to merge for most repos, but carries a latent pipe-buffer deadlock risk in runGitCommand that could hang background queue threads on large repositories.
  • The overall design is sound and the generation-based guard correctly prevents stale updates. The confidence is lowered primarily because runGitCommand reads from the stdout pipe only after waitUntilExit(), violating the standard rule for avoiding pipe-buffer deadlocks. For common repos this is fine, but it is a correctness hazard under adverse conditions. The unconditional scheduling of all six probes is a secondary resource-efficiency concern.
  • Sources/TabManager.swift — specifically runGitCommand (lines 977-998) and scheduleInitialWorkspaceGitMetadataRefresh (lines 882-898)

Important Files Changed

Filename Overview
Sources/TabManager.swift Core of the PR: adds a multi-probe git metadata refresh pipeline (6 probes over ~10 s) for workspaces with an explicit cwd, plus background-load bookkeeping. The runGitCommand helper uses waitUntilExit() before draining stdout/stderr, which risks a pipe-buffer deadlock on large repos. All probes are scheduled unconditionally with no early-cancellation path, leading to wasted subprocess forks after early termination.
Sources/ContentView.swift Adds a SwiftUI .task(id:) loop that polls stepBackgroundWorkspacePrime every 50 ms for up to 2 s, wires pendingBackgroundWorkspaceLoadIds into the mounted-workspace pin set, and triggers reconcileMountedWorkspaceIds on load-id changes. Logic looks correct; handoffPinnedIds / pinnedIds split is clean.
Sources/TerminalController.swift Minor refactor: inline requestBackgroundSurfaceStartIfNeeded() calls replaced by the new eagerLoadTerminal parameter. Functionally equivalent to the pre-PR code; no issues.
Sources/Workspace.swift Adds requestBackgroundTerminalSurfaceStartIfNeeded() and hasLoadedTerminalSurface() helpers. hasLoadedTerminalSurface() returns true when there are no terminal panels, which is intentional (avoids blocking on layout-less workspaces). No issues.
tests_v2/test_cli_new_workspace_external_git_branch_refresh.py New end-to-end regression test: creates a temp repo, opens a background workspace pointing at it, waits for the main branch to appear in the sidebar, then does an external git checkout -b feature/external-refresh and waits up to 15 s for the sidebar to reflect the new branch. Test structure is clear and cleanup is handled in a finally block.

Sequence Diagram

sequenceDiagram
    participant TC as TerminalController
    participant TM as TabManager (MainActor)
    participant Q as initialWorkspaceGitProbeQueue
    participant Git as git subprocess
    participant CV as ContentView (SwiftUI)

    TC->>TM: addWorkspace(cwd, eagerLoadTerminal: true)
    TM->>TM: scheduleInitialWorkspaceGitMetadataRefresh(workspaceId, panelId, dir)
    TM->>TM: requestBackgroundWorkspaceLoad(workspaceId)
    TM-->>CV: @Published pendingBackgroundWorkspaceLoadIds updated
    CV->>CV: reconcileMountedWorkspaceIds() — pins workspace
    CV->>CV: .task(id: tab.id) fires → primeBackgroundWorkspaceIfNeeded()

    loop 6 probes at [0, 0.5, 1.5, 3.0, 6.0, 10.0]s
        Q->>Git: git -C dir branch --show-current
        Git-->>Q: branch name
        Q->>Git: git -C dir status --porcelain -uno
        Git-->>Q: dirty status
        Q->>TM: Task @MainActor applyInitialWorkspaceGitMetadataSnapshot(snapshot)
        TM->>TM: updatePanelGitBranch / clearPanelPullRequest
    end

    loop every 50 ms (up to 2 s)
        CV->>CV: stepBackgroundWorkspacePrime(workspaceId)
        CV->>CV: requestBackgroundTerminalSurfaceStartIfNeeded()
        alt surface loaded
            CV->>TM: completeBackgroundWorkspaceLoad(workspaceId)
            TM-->>CV: pendingBackgroundWorkspaceLoadIds updated → task cancelled
        else timeout
            CV->>TM: completeBackgroundWorkspaceLoad(workspaceId)
        end
    end
Loading

Last reviewed commit: 1291776

Copy link
Copy Markdown

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

Choose a reason for hiding this comment

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

1 issue found across 5 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="Sources/TabManager.swift">

<violation number="1" location="Sources/TabManager.swift:991">
P1: `runGitCommand` can deadlock by calling `waitUntilExit()` before reading piped stdout.</violation>
</file>

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

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
Sources/ContentView.swift (1)

2604-2613: Extract priming timing values into named constants.

2.0 and 50_000_000 are magic numbers in a central flow. Pulling them into local policy constants will make tuning/tests clearer and safer.

♻️ Suggested refactor
+    private enum BackgroundWorkspacePrimePolicy {
+        static let timeoutSeconds: TimeInterval = 2.0
+        static let pollIntervalNanoseconds: UInt64 = 50_000_000
+    }
+
     private func primeBackgroundWorkspaceIfNeeded(workspaceId: UUID) async {
@@
-        let timeout = Date().addingTimeInterval(2.0)
+        let timeout = Date().addingTimeInterval(BackgroundWorkspacePrimePolicy.timeoutSeconds)
         while !Task.isCancelled {
@@
-                    try? await Task.sleep(nanoseconds: 50_000_000)
+                    try? await Task.sleep(nanoseconds: BackgroundWorkspacePrimePolicy.pollIntervalNanoseconds)
                     continue
                 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/ContentView.swift` around lines 2604 - 2613, The loop uses magic
numbers for the priming timeout and poll sleep; extract these into named
constants (e.g. primingTimeoutSeconds and primingPollSleepNanoseconds) near the
surrounding function so the timeout initialization (currently
Date().addingTimeInterval(2.0)) and the Task.sleep(nanoseconds: 50_000_000) call
reference those constants instead of raw literals; update any related
comparisons and comments in the block that calls
stepBackgroundWorkspacePrime(workspaceId:) and checks Task.isCancelled to use
the new constants for clarity and easier tuning/testing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests_v2/test_cli_new_workspace_external_git_branch_refresh.py`:
- Line 19: SOCKET_PATH currently falls back to the untagged
"/tmp/cmux-debug.sock"; remove the default and require a tagged socket value by
reading CMUX_SOCKET without a fallback and validating it matches the tagged
pattern (starts with "/tmp/cmux-debug-" and contains a tag and ".sock"); update
the SOCKET_PATH assignment in
tests_v2/test_cli_new_workspace_external_git_branch_refresh.py and add a short
validation that raises/prints an error and exits the test if CMUX_SOCKET is
unset or does not match the "/tmp/cmux-debug-<tag>.sock" format so the test
cannot connect to an untagged instance.

---

Nitpick comments:
In `@Sources/ContentView.swift`:
- Around line 2604-2613: The loop uses magic numbers for the priming timeout and
poll sleep; extract these into named constants (e.g. primingTimeoutSeconds and
primingPollSleepNanoseconds) near the surrounding function so the timeout
initialization (currently Date().addingTimeInterval(2.0)) and the
Task.sleep(nanoseconds: 50_000_000) call reference those constants instead of
raw literals; update any related comparisons and comments in the block that
calls stepBackgroundWorkspacePrime(workspaceId:) and checks Task.isCancelled to
use the new constants for clarity and easier tuning/testing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 62edac6c-2ed4-4e0c-81a2-142330b3700c

📥 Commits

Reviewing files that changed from the base of the PR and between 29054dc and 1291776.

📒 Files selected for processing (5)
  • Sources/ContentView.swift
  • Sources/TabManager.swift
  • Sources/TerminalController.swift
  • Sources/Workspace.swift
  • tests_v2/test_cli_new_workspace_external_git_branch_refresh.py

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