Skip to content

Fix background terminal startup for unfocused workspaces - #920

Merged
austinywang merged 1 commit into
mainfrom
issue-915-workspace-terminal-not-loaded
Mar 5, 2026
Merged

austinywang merged 1 commit into
mainfrom
issue-915-workspace-terminal-not-loaded

Conversation

@austinywang

@austinywang austinywang commented Mar 5, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • request terminal surface background startup when creating a workspace without focusing it
  • request terminal surface background startup when adding a tab without focusing it
  • add a regression test for new-workspace --cwd metadata preloading without focus changes

Testing

  • not run in this session

Summary by cubic

Fix background terminal startup for unfocused workspaces and tabs so sidebar metadata preloads without switching focus. Addresses issue #915 where new-workspace --cwd didn’t load git metadata unless focused.

  • Bug Fixes
    • Start the terminal surface in the background when creating a workspace or tab without focus to trigger metadata preload.
    • Add a regression test to verify new-workspace --cwd preloads git branch metadata and preserves the currently selected workspace.

Written for commit 7efc670. Summary will update on new commits.

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Fixed background surface initialization to automatically start when creating new workspaces or tabs without focus.
  • Tests

    • Added regression test validating that new-workspace --cwd preloads sidebar metadata without changing the active workspace.

@vercel

vercel Bot commented Mar 5, 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 Mar 5, 2026 3:17am

@coderabbitai

coderabbitai Bot commented Mar 5, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Changes introduce background surface startup logic when creating unfocused workspaces or tabs in TerminalController, and add a comprehensive test validating that the new-workspace CLI command preloads sidebar metadata without changing the currently selected workspace.

Changes

Cohort / File(s) Summary
Background Surface Startup Logic
Sources/TerminalController.swift
Added conditional logic to trigger requestBackgroundSurfaceStartIfNeeded() on the focused terminal panel when creating new workspaces or tabs without focus, ensuring background surface activity starts without explicit focus changes.
CLI Workspace Metadata Test
tests_v2/test_cli_new_workspace_background_metadata.py
New regression test validating that new-workspace --cwd preloads sidebar metadata (cwd and git branch) in the background without altering the currently selected workspace. Includes helper functions for CLI invocation, sidebar state parsing, Git repository setup, and metadata polling with timeout handling.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A terminal's dance, now more graceful and spry,
Workspaces bloom in the background so sly,
Git branches whisper their names from afar,
While tests keep the rabbit's code shining like stars! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: adding background terminal startup logic for unfocused workspaces and tabs in TerminalController.swift.

✏️ 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-workspace-terminal-not-loaded

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 2 files

@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: 2

🧹 Nitpick comments (1)
tests_v2/test_cli_new_workspace_background_metadata.py (1)

70-75: Use a monotonic clock for timeout loops.

At Line 71 and Line 74, time.time() makes the timeout sensitive to wall-clock jumps; time.monotonic() is safer for polling deadlines.

Proposed refactor
-    deadline = time.time() + timeout
+    deadline = time.monotonic() + timeout
@@
-    while time.time() < deadline:
+    while time.monotonic() < deadline:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests_v2/test_cli_new_workspace_background_metadata.py` around lines 70 - 75,
The timeout loop in _wait_for_sidebar_git_branch uses time.time() for computing
deadline and checking the loop condition, which is susceptible to wall-clock
changes; replace both uses of time.time() with time.monotonic() (i.e., compute
deadline = time.monotonic() + timeout and loop while time.monotonic() <
deadline) so the polling deadline is monotonic and robust.
🤖 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_background_metadata.py`:
- Around line 36-42: The test currently picks the newest file from a broad glob
(using the candidates list) which can return unrelated builds; make discovery
deterministic by first honoring an explicit CMUXTERM_CLI environment variable
(validate it is a file and executable and return it), and only if unset, gather
glob candidates, filter them to files/executables as before, then validate each
candidate by invoking the CLI (e.g., subprocess.run([candidate, "--version"] or
a known probe) and accept only those that respond with the expected
version/identifier, and finally deterministically pick the candidate by a stable
key (e.g., sorted pathname) instead of os.path.getmtime; raise cmuxError if none
pass validation.
- Around line 179-183: The cleanup block currently swallows all exceptions;
change the except to catch the specific cmuxError raised by _run_cli and log the
failure instead of silent pass. Locate the cleanup conditional that checks
created_workspace and replace the bare "except Exception: pass" with "except
cmuxError as e:" (or the proper import) and call a logger (or
pytest.fail/logging.error) to record e so cleanup failures are visible.

---

Nitpick comments:
In `@tests_v2/test_cli_new_workspace_background_metadata.py`:
- Around line 70-75: The timeout loop in _wait_for_sidebar_git_branch uses
time.time() for computing deadline and checking the loop condition, which is
susceptible to wall-clock changes; replace both uses of time.time() with
time.monotonic() (i.e., compute deadline = time.monotonic() + timeout and loop
while time.monotonic() < deadline) so the polling deadline is monotonic and
robust.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 59a23040-1aba-49ed-b4fd-11fb643ef367

📥 Commits

Reviewing files that changed from the base of the PR and between 39a0da2 and 7efc670.

📒 Files selected for processing (2)
  • Sources/TerminalController.swift
  • tests_v2/test_cli_new_workspace_background_metadata.py

Comment on lines +36 to +42
candidates = glob.glob(os.path.expanduser("~/Library/Developer/Xcode/DerivedData/**/Build/Products/Debug/cmux"), recursive=True)
candidates += glob.glob("/tmp/cmux-*/Build/Products/Debug/cmux")
candidates = [p for p in candidates if os.path.isfile(p) and os.access(p, os.X_OK)]
if not candidates:
raise cmuxError("Could not locate cmux CLI binary; set CMUXTERM_CLI")
candidates.sort(key=lambda p: os.path.getmtime(p), reverse=True)
return candidates[0]

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

Make CLI binary discovery deterministic.

At Line 36-Line 42, picking the “most recently modified” cmux from broad globs can select an unrelated build and make this regression test validate the wrong binary.

Proposed hardening
-    candidates = [p for p in candidates if os.path.isfile(p) and os.access(p, os.X_OK)]
-    if not candidates:
-        raise cmuxError("Could not locate cmux CLI binary; set CMUXTERM_CLI")
-    candidates.sort(key=lambda p: os.path.getmtime(p), reverse=True)
-    return candidates[0]
+    candidates = [p for p in candidates if os.path.isfile(p) and os.access(p, os.X_OK)]
+    if not candidates:
+        raise cmuxError("Could not locate cmux CLI binary; set CMUXTERM_CLI")
+    if len(candidates) == 1:
+        return candidates[0]
+    raise cmuxError(
+        "Multiple cmux CLI binaries found; set CMUXTERM_CLI to the intended binary"
+    )
🧰 Tools
🪛 Ruff (0.15.2)

[error] 37-37: Probable insecure usage of temporary file or directory: "/tmp/cmux-*/Build/Products/Debug/cmux"

(S108)


[warning] 40-40: Avoid specifying long messages outside the exception class

(TRY003)

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

In `@tests_v2/test_cli_new_workspace_background_metadata.py` around lines 36 - 42,
The test currently picks the newest file from a broad glob (using the candidates
list) which can return unrelated builds; make discovery deterministic by first
honoring an explicit CMUXTERM_CLI environment variable (validate it is a file
and executable and return it), and only if unset, gather glob candidates, filter
them to files/executables as before, then validate each candidate by invoking
the CLI (e.g., subprocess.run([candidate, "--version"] or a known probe) and
accept only those that respond with the expected version/identifier, and finally
deterministically pick the candidate by a stable key (e.g., sorted pathname)
instead of os.path.getmtime; raise cmuxError if none pass validation.

Comment on lines +179 to +183
if created_workspace:
try:
_run_cli(cli, ["close-workspace", "--workspace", created_workspace])
except Exception:
pass

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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, check the file exists and view the relevant lines with context
head -200 tests_v2/test_cli_new_workspace_background_metadata.py | tail -30

Repository: manaflow-ai/cmux

Length of output: 1118


🏁 Script executed:

# Get exact line count and content around lines 179-183
wc -l tests_v2/test_cli_new_workspace_background_metadata.py

Repository: manaflow-ai/cmux

Length of output: 118


🏁 Script executed:

# View lines 170-190 to get proper context
sed -n '170,190p' tests_v2/test_cli_new_workspace_background_metadata.py

Repository: manaflow-ai/cmux

Length of output: 760


🏁 Script executed:

# Check what imports are at the top of the file
head -30 tests_v2/test_cli_new_workspace_background_metadata.py

Repository: manaflow-ai/cmux

Length of output: 745


🏁 Script executed:

# Search for cmuxError in the codebase to understand what it is
rg "cmuxError" --type py

Repository: manaflow-ai/cmux

Length of output: 50372


🏁 Script executed:

# Search for _run_cli definition to understand what exceptions it raises
rg "def _run_cli" --type py -A 10

Repository: manaflow-ai/cmux

Length of output: 15013


🏁 Script executed:

# Look for similar exception handling patterns in test files
rg "except.*pass" tests_v2/ --type py -B 2 -A 1

Repository: manaflow-ai/cmux

Length of output: 42


Don't silently swallow workspace cleanup failures.

The bare except Exception: pass at lines 182-183 hides cleanup failures and makes test flakes harder to diagnose. Since _run_cli raises cmuxError on failure, catch that specifically and log the error instead of silencing it.

Proposed fix
         if created_workspace:
             try:
                 _run_cli(cli, ["close-workspace", "--workspace", created_workspace])
-            except Exception:
-                pass
+            except cmuxError as exc:
+                print(
+                    f"WARN: failed to close workspace {created_workspace!r}: {exc}",
+                    file=sys.stderr,
+                )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if created_workspace:
try:
_run_cli(cli, ["close-workspace", "--workspace", created_workspace])
except Exception:
pass
if created_workspace:
try:
_run_cli(cli, ["close-workspace", "--workspace", created_workspace])
except cmuxError as exc:
print(
f"WARN: failed to close workspace {created_workspace!r}: {exc}",
file=sys.stderr,
)
🧰 Tools
🪛 Ruff (0.15.2)

[error] 182-183: try-except-pass detected, consider logging the exception

(S110)


[warning] 182-182: Do not catch blind exception: Exception

(BLE001)

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

In `@tests_v2/test_cli_new_workspace_background_metadata.py` around lines 179 -
183, The cleanup block currently swallows all exceptions; change the except to
catch the specific cmuxError raised by _run_cli and log the failure instead of
silent pass. Locate the cleanup conditional that checks created_workspace and
replace the bare "except Exception: pass" with "except cmuxError as e:" (or the
proper import) and call a logger (or pytest.fail/logging.error) to record e so
cleanup failures are visible.

@austinywang
austinywang merged commit 8193e55 into main Mar 5, 2026
13 checks passed
@greptile-apps

greptile-apps Bot commented Mar 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds calls to requestBackgroundSurfaceStartIfNeeded() in two code paths where workspaces or tabs are created without focus:

  • The v2 workspace.create API path (when shouldFocus is false)
  • The legacy newWorkspace socket command path (when focus is false)

The changes are minimal and defensively guarded with if !shouldFocus/focus, let terminalPanel = ws.focusedTerminalPanel. The accompanying regression test verifies that new-workspace --cwd preloads sidebar git metadata (branch, cwd) without changing the focused workspace, and includes proper cleanup and polling with timeout.

The code changes are safe—no crash risk from merging—and the test structure is sound.

Confidence Score: 4/5

  • No crash risk; minimal, defensively guarded changes with no side effects.
  • The Swift changes are safe, straightforward, and properly guarded. The regression test is well-structured. Without test execution results, confidence is not perfect, but the implementation itself is sound and low-risk.
  • No files require special attention. Both changed files are straightforward.

Last reviewed commit: 7efc670

bn-l pushed a commit to bn-l/cmux that referenced this pull request Apr 3, 2026

This branch was successfully deployed

1 active deployment
Preview — 7efc670e Deployed Mar 5, 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.

1 participant