Skip to content

Fix sidebar-triggered main-actor terminal teardown hang - #9358

Merged
austinywang merged 12 commits into
mainfrom
issue-9220-sidebar-click-hang
Aug 10, 2026
Merged

austinywang merged 12 commits into
mainfrom
issue-9220-sidebar-click-hang

Conversation

@austinywang

@austinywang austinywang commented Aug 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • route explicit terminal surface teardown through TerminalSurfaceRuntimeTeardownCoordinator
  • run close/deinit frees through a two-slot bounded pool so one stuck Ghostty join cannot strand later closes
  • keep callback, manual-I/O, and byte-tee userdata retained until native teardown returns
  • make the native-free test gate surface-scoped and one-shot

Closes #9220.

Root cause and symbolication chain

The attachment is a 12.79-second hang stackshot, not a crash log. Its shipped cmux image UUID is 6FD8C9CB-CBA6-3651-9733-027FBC2490CB, loaded at 0x100c44000. In all 17 samples the main actor is blocked in __ulock_wait from the Swift concurrency job whose cmux return address is 0x104943628 (unslid 0x103cff628, image offset 0x3cff628). The turnstile owner is Ghostty renderer thread 0x6598191.

The public release does not include dSYMs, and a same-commit rebuild produced a different UUID, so that nonmatching dSYM was not used as false symbolication. Instead, the shipped arm64 instructions at 0x103cff628 were compared with the pinned Ghostty bb30526c archive object. The instruction sequence is byte-identical to _Surface.deinit + 156; the sampled address is the return site of the first pthread_join. Ghostty source at src/Surface.zig:847-867 identifies that first join as self.renderer_thr.join().

The complete blocking chain is:

@MainActor Swift Task → ghostty_surface_free → Ghostty Surface.deinit → renderer_thr.join() → renderer thread does not finish → main actor hangs.

Release TerminalSurface.teardownSurface() scheduled ghostty_surface_free inside Task { @MainActor in ... }. Current main already routed deinit and agent-hibernation frees through TerminalSurfaceRuntimeTeardownCoordinator; explicit teardown was the remaining production bypass. Sidebar selection/restore churn can reach that explicit close path, making the sidebar click the visible trigger.

Fix

TerminalSurface.teardownSurface() now detaches all main-actor ownership first, then submits the pointer and its retained callback userdata to the injected teardown coordinator. Native free runs away from the main actor, and callback userdata is released only after Ghostty's renderer/IO joins return.

The coordinator now owns two independently startable close/deinit execution slots. A request holds its slot until native free, userdata release, and ticket completion are all finished. One permanently blocked Ghostty join therefore leaves the other slot able to drain later closes. Hibernation admission and its two isolated queues remain a separate bounded pool.

Invariants after this change:

  • no production ghostty_surface_free or renderer-thread join runs on the main actor
  • one stuck close cannot strand all later close/deinit requests
  • callback userdata outlives every Ghostty callback thread joined by native free

Related PR #8905 independently targets this hang class but also contains unrelated hook/environment work; this PR is the self-contained terminal teardown fix and regression.

Regression proof

The regression commits are intentionally split from their fixes:

  1. 0a42b05f6b adds the main-actor responsiveness regression. On the AWS macOS 15.7.4 builder it failed in 1.053s before 77ab34b3a4 routed explicit teardown through the coordinator.
  2. 7fcc434fe6 adds the stuck-close isolation regression. It failed with both later close tickets timing out behind the first blocked free before d5c5c3e8ad introduced bounded close slots.
  3. 40578022e7 adds the surface-scoped gate regression. It failed after delaying an unrelated surface by 5.119s before d5c5c3e8ad scoped the gate by pointer identity.

AWS verification after the fix:

  • TerminalSurfaceRuntimeTeardownCoordinatorTests: 7/7 passed
  • TerminalSurfaceTeardownCallbackLifetimeTests: 10/10 passed

No local xcodebuild test or XCUITest was run.

Localization audit

No user-facing strings, settings, menus, schema text, docs, or help text changed; no localization catalog update is required.

Summary by CodeRabbit

  • Performance

    • Improved terminal surface cleanup by allowing up to two close operations to run concurrently.
    • Prevented a stalled cleanup from delaying subsequent terminal closures.
    • Kept the application’s main interface responsive during cleanup.
  • Bug Fixes

    • Improved reliability of resource release and cleanup callbacks during terminal teardown.
  • Tests

    • Added coverage for concurrent cleanup, blocked operations, surface isolation, and main-interface responsiveness.

Note

High Risk
Changes terminal native teardown and threading around ghostty_surface_free on critical UI paths; regressions could cause hangs, leaks, or use-after-free on IO callbacks, though ordering and concurrency are heavily tested.

Overview
Fixes main-actor hangs when closing terminals (e.g. sidebar churn) by removing the last production path that called ghostty_surface_free on the main actor inside teardownSurface(). Explicit teardown now matches deinit and agent-hibernation: detach ownership on the main actor, then enqueue native free on TerminalSurfaceRuntimeTeardownCoordinator.

Close/deinit teardown is no longer fully serialized on one utility worker. The coordinator runs up to two concurrent close slots (maximumConcurrentCloseTeardownCount), each on its own utility DispatchQueue, so one stuck Ghostty renderer join cannot block every later close. The execution lane serializedClose is renamed boundedClose.

Tests add a stuck-close regression, a main-actor responsiveness check while native free is blocked, and surface-scoped blocking stubs so the test gate does not intercept unrelated frees.

Reviewed by Cursor Bugbot for commit f6bd09f. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026 •

Copy link
Copy Markdown

Review Change Stack

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

Changes

Terminal surface teardown now uses two bounded concurrent close-teardown slots. Native freeing and resource release remain coordinated by runtimeTeardown. Tests add targeted blocking controls and verify teardown concurrency and main-actor responsiveness.

Terminal teardown

Layer / File(s) Summary
Schedule bounded close teardown
Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Lifecycle/*, Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Runtime/TerminalSurfaceRuntimeDependencies.swift
The coordinator replaces serialized close processing with two reusable teardown slots. Execution-lane and teardown documentation describe bounded close processing.
Enqueue surface teardown
Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Surface/TerminalSurface+RuntimeLifecycle.swift
teardownSurface() queues native freeing with its callback context, manual I/O context, and byte-tee lease.
Validate blocked native freeing
Packages/macOS/CmuxTerminal/Tests/CmuxTerminalTests/*, Packages/macOS/CmuxTerminal/Tests/GhosttyRuntimeTestStubs/*
Test stubs block one targeted ghostty_surface_free call. Tests verify unrelated frees, later close completion, isolated teardown interaction, callback cleanup, and main-actor responsiveness.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TeardownTest
  participant TerminalSurface
  participant RuntimeTeardown
  participant GhosttySurface
  participant MainActor
  TeardownTest->>GhosttySurface: Enable blocking for target surface
  TeardownTest->>TerminalSurface: Call teardownSurface()
  TerminalSurface->>RuntimeTeardown: Enqueue runtime teardown
  RuntimeTeardown->>GhosttySurface: Run ghostty_surface_free(...)
  TeardownTest->>MainActor: Run responsiveness probe
  TeardownTest->>GhosttySurface: Release and reset blocking gate
Loading

Possibly related PRs

  • manaflow-ai/cmux#8050: Both change deferred native surface teardown and callback-resource lifetime handling.
  • manaflow-ai/cmux#8674: Both modify callback-lifetime teardown tests and surface-free observation.
  • manaflow-ai/cmux#8905: This change extends the terminal teardown coordinator from serialized to bounded concurrent teardown.

Suggested reviewers: azooz2003-bit

🚥 Pre-merge checks | ✅ 24 | ❌ 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 (24 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes address issue #9220 by preventing sidebar-triggered terminal teardown from blocking the main actor.
Out of Scope Changes check ✅ Passed The coordinator changes, teardown tests, and native test stubs directly support the linked issue and stated teardown objectives.
Cmux Swift Actor Isolation ✅ Passed The production diff keeps bounded teardown state inside an actor, uses the existing Sendable transport, and routes from an intentional @MainActor UI model; no new implicit MainActor model, protocol...
Cmux Swift Blocking Runtime ✅ Passed The production Swift diff adds actor-managed slot admission and utility-queue dispatch, but no new waits, sleeps, locks, polling, or delayed dispatch; semaphores and pthread waits are test-only sca...
Cmux Browser Automation Off-Main ✅ Passed The PR changes only terminal teardown code and tests; both rule-scoped browser routing files are unchanged, so browser automation routing is not introduced or worsened.
Cmux Expensive Synchronous Load ✅ Passed The production Swift diff only changes terminal teardown scheduling and bounded utility queues; added-line and changed-file scans found no agent-history loaders, file scans, or JSON/JSONL parsing.
Cmux Cache Substitution Correctness ✅ Passed The production diff changes terminal teardown scheduling and bounded native frees only; it contains no persistence, history, undo, snapshot, or cache substitution.
Cmux No Hacky Sleeps ✅ Passed The PR changes only Swift and C files; it introduces no TypeScript, JavaScript, shell, or covered build/runtime-script delays. C synchronization is test-only scaffolding.
Cmux Algorithmic Complexity ✅ Passed The production diff adds fixed two-slot scheduling and no nested scans, repeated filtering/sorting, or data-store joins; the existing queue removeFirst pattern is unchanged debt, not worsened.
Cmux Swift Concurrency ✅ Passed The new utility queues isolate blocking ghostty_surface_free calls, not ordinary async work; coordinator tasks complete teardown tickets, and test-only Dispatch synchronization is allowed.
Cmux Swift @Concurrent ✅ Passed The diff adds no invalid @concurrent use; native freeing crosses explicit utility DispatchQueue boundaries, while new async coordination is actor-isolated and teardownSurface only enqueues work.
Cmux Swift Package Boundaries ✅ Passed Production changes remain in the CmuxTerminal SwiftPM target and are Ghostty-specific TerminalSurface lifecycle glue; tests are test-only, with no new cross-surface domain logic in an app-root target.
Cmux Swiftpm Lockfiles ✅ Passed The PR range changes only teardown Swift sources/tests and C stubs; no Package.swift, Package.resolved, .gitignore, workflow, or Xcode package-reference files changed.
Cmux Swift Logging ✅ Passed The PR diff adds no print, debugPrint, dump, NSLog, Logger, file, or stdout logging; existing DEBUG logDebugEvent calls are unchanged.
Cmux User-Facing Error Privacy ✅ Passed Production diff adds teardown scheduling and internal debug/queue labels only; no user-facing errors, alerts, command output, or sensitive payloads were added. Test messages are test-only.
Cmux Full Internationalization ✅ Passed The production diff changes teardown orchestration and developer comments only; added literals are an internal reason and DispatchQueue label. No user-facing text or localization/catalog files chan...
Cmux Swiftui State Layout ✅ Passed The changed files contain no SwiftUI views, state wrappers, GeometryReader, lazy/list row stores, or render-time state writes; the only added MainActor task is test synchronization.
Cmux Architecture Rethink ✅ Passed The actor-owned coordinator is the single teardown owner, with explicit bounded-slot and userdata-lifetime invariants; utility queues are the native bridge, and new locks/semaphores are test-only.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed The PR changes terminal teardown coordination and test stubs only. Its Swift diff adds no NSWindow, NSPanel, NSWindowController, Window, WindowGroup, or close-shortcut code; the rule is not applica...
Cmux Source Artifacts ✅ Passed The PR changes only 9 Swift, C, and header files under CmuxTerminal Sources/Tests; no artifact directories, logs, media, binary files, or scratch paths enter the diff.
Cmux No Test Or Debug Seam In Production Source ✅ Passed PR production Swift additions only implement bounded teardown and add a concurrency constant; no new test/debug seam or test-build guard was added. Existing DEBUG seams are unchanged.
Cmux No Ambient Global State ✅ Passed The production diff adds only the allowed static let constant; new mutable queues and slot state live on the injectable TerminalSurfaceRuntimeTeardownCoordinator actor, with no new global functions...
Title check ✅ Passed The title clearly identifies the main change: fixing a main-actor terminal teardown hang triggered by sidebar actions.
Description check ✅ Passed The description thoroughly explains the fix, root cause, testing, verification results, and scope, but omits the demo video, review trigger, and checklist sections.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-9220-sidebar-click-hang

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.

@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 and verified against the latest diff

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="Packages/macOS/CmuxTerminal/Tests/GhosttyRuntimeTestStubs/GhosttyRuntimeTestStubs.c">

<violation number="1" location="Packages/macOS/CmuxTerminal/Tests/GhosttyRuntimeTestStubs/GhosttyRuntimeTestStubs.c:60">
P2: The condition waits in wait_until_started and the gated ghostty_surface_free have no timeout, so a regression that never routes the surface free to this stub (or never releases the gate) will hang the whole test process indefinitely rather than fail with a clear signal. Consider bounding both waits (e.g. pthread_cond_timedwait with a deadline) and returning an error/asserting so CI fails instead of stalling.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

@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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@Packages/macOS/CmuxTerminal/Tests/CmuxTerminalTests/TerminalSurfaceTeardownCallbackLifetimeTests.swift`:
- Around line 8-19: Replace the four `@_silgen_name` declarations in
TerminalSurfaceTeardownCallbackLifetimeTests with a normal import of the
GhosttyRuntimeTestStubs module, then call the exposed C bindings directly.
Preserve the existing binding names and test behavior while removing the manual
Swift symbol wrappers.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d9cbdb34-22f9-4527-85f3-5cb3c3d7aabe

📥 Commits

Reviewing files that changed from the base of the PR and between 649cb7f and 4ba55e7.

📒 Files selected for processing (4)
  • Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Surface/TerminalSurface+RuntimeLifecycle.swift
  • Packages/macOS/CmuxTerminal/Tests/CmuxTerminalTests/TerminalSurfaceTeardownCallbackLifetimeTests.swift
  • Packages/macOS/CmuxTerminal/Tests/GhosttyRuntimeTestStubs/GhosttyRuntimeTestStubs.c
  • Packages/macOS/CmuxTerminal/Tests/GhosttyRuntimeTestStubs/include/GhosttyRuntimeTestStubs.h

@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

Re-trigger cubic

@cursor

cursor Bot commented Aug 1, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@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.

2 issues found and verified against the latest diff

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="Packages/macOS/CmuxTerminal/Tests/CmuxTerminalTests/TerminalSurfaceRuntimeTeardownCoordinatorTests.swift">

<violation number="1" location="Packages/macOS/CmuxTerminal/Tests/CmuxTerminalTests/TerminalSurfaceRuntimeTeardownCoordinatorTests.swift:167">
P2: The new `stuckCloseFreeDoesNotStrandLaterCloses` test asserts that later closes complete within one second while the first native free is gate-blocked, but the coordinator's default `.serializedClose` lane runs all requests through a single serialized worker. When the first request's `freeSurface` blocks, that worker is stuck and cannot dequeue the later tickets, so `await ticket.wait(timeout: .seconds(1))` returns false and the test's `#expect` fails (it also burn ~2s of wall time). The concurrency the test wants only exists on the isolated-hibernation lane. The test should either use the isolated-hibernation lane (mirroring `stuckHibernationFreeDoesNotStrandAnotherAdmissionOrClose`) or flip its expectations to assert that a stuck close in the serialized lane does strand later closes, otherwise this added test is red.</violation>

<violation number="2" location="Packages/macOS/CmuxTerminal/Tests/CmuxTerminalTests/TerminalSurfaceRuntimeTeardownCoordinatorTests.swift:173">
P3: Read freed pointers via `recorder.waitForFreeCount(laterTickets.count)` before the equality check, since the free closures record through spawned `Task { await recorder.record(bits) }` and ticket.wait can resolve before those Tasks run. The file's sibling test already uses this helper for the same reason; checking `recorder.freed` directly here risks a flaky partial-set comparison.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

@cursor

cursor Bot commented Aug 1, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@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.

2 issues found and verified against the latest diff

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="Packages/macOS/CmuxTerminal/Tests/GhosttyRuntimeTestStubs/GhosttyRuntimeTestStubs.c">

<violation number="1" location="Packages/macOS/CmuxTerminal/Tests/GhosttyRuntimeTestStubs/GhosttyRuntimeTestStubs.c:41">
P3: cmux_test_ghostty_runtime_stubs_reset() does not clear the new surface-free blocking state, so a test that tears down without running blocking_reset (e.g. a test failure before the defer, or a future test that forgets it) leaves should_block=true and makes unrelated tests in the same process block 5s per free. Consider folding the blocking-state reset into the general reset, or having ghostty_surface_free auto-clear the flag after it unblocks, to keep failure isolation for the rest of the suite.</violation>
</file>

<file name="Packages/macOS/CmuxTerminal/Tests/CmuxTerminalTests/TerminalSurfaceTeardownCallbackLifetimeTests.swift">

<violation number="1" location="Packages/macOS/CmuxTerminal/Tests/CmuxTerminalTests/TerminalSurfaceTeardownCallbackLifetimeTests.swift:41">
P2: This test will fail every run: the ghostty test stub's free gate is global (cmux_test_surface_free_should_block), so ghostty_surface_free(0x7542) gets blocked for the 5s condvar deadline and the `< .seconds(1)` assertion fails, adding a 5s stall to the serialized @MainActor suite. The 'does not intercept another surface' premise only holds if the gate is made surface-aware (gate the specific teardown surface), otherwise the assertion can never pass. Please make blocking_start take the expected surface and only block ghostty_surface_free for that pointer, then this test validates the intended behavior.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Lifecycle/TerminalSurfaceRuntimeTeardownCoordinator.swift`:
- Around line 227-261: Update the bounded close-teardown flow around
startAvailableCloseTeardowns and finishCloseTeardown to expose when
queuedCloseRequests grows beyond a configured threshold, such as by reporting
the queue depth through the existing logging or telemetry mechanism. Preserve
the current slot behavior and main-actor responsiveness; make systemic
native-free hangs observable rather than silently leaving later requests queued
indefinitely.

In
`@Packages/macOS/CmuxTerminal/Tests/CmuxTerminalTests/TerminalSurfaceTeardownCallbackLifetimeTests.swift`:
- Around line 31-48: Replace the ContinuousClock elapsed-time assertion in
surfaceFreeGateDoesNotInterceptAnotherRuntimeSurface with a completion signal
and deadline-bounded wait, following the existing pattern in
teardownSurfaceKeepsMainActorResponsiveWhileNativeFreeIsBlocked. Signal
completion after ghostty_surface_free(unrelatedSurface) returns, then assert the
signal completes before the deadline while preserving the test’s cleanup
behavior.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8f28fce9-3b4b-4cf5-b3af-43d07339aaa7

📥 Commits

Reviewing files that changed from the base of the PR and between 96c6c1f and d5c5c3e.

📒 Files selected for processing (8)
  • Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Lifecycle/TerminalSurfaceRuntimeTeardownCoordinator.swift
  • Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Lifecycle/TerminalSurfaceRuntimeTeardownExecutionLane.swift
  • Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Lifecycle/TerminalSurfaceRuntimeTeardownTicket.swift
  • Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Runtime/TerminalSurfaceRuntimeDependencies.swift
  • Packages/macOS/CmuxTerminal/Tests/CmuxTerminalTests/TerminalSurfaceRuntimeTeardownCoordinatorTests.swift
  • Packages/macOS/CmuxTerminal/Tests/CmuxTerminalTests/TerminalSurfaceTeardownCallbackLifetimeTests.swift
  • Packages/macOS/CmuxTerminal/Tests/GhosttyRuntimeTestStubs/GhosttyRuntimeTestStubs.c
  • Packages/macOS/CmuxTerminal/Tests/GhosttyRuntimeTestStubs/include/GhosttyRuntimeTestStubs.h

@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.

3 issues found and verified against the latest diff

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="Packages/macOS/CmuxTerminal/Tests/GhosttyRuntimeTestStubs/GhosttyRuntimeTestStubs.c">

<violation number="1" location="Packages/macOS/CmuxTerminal/Tests/GhosttyRuntimeTestStubs/GhosttyRuntimeTestStubs.c:48">
P3: The 5s blocking-gate timeout is computed from CLOCK_REALTIME, so a wall-clock adjustment (NTP step, manual/timezone change) on the CI builder can arbitrarily extend the fallback wait (clock stepped backward) or fire it early (stepped forward). The deadlock guard is exactly the safety net this PR relies on for teardown hangs, so it shouldn't be at the mercy of wall-clock jumps. Consider creating the condvar with a CLOCK_MONOTONIC attr (pthread_condattr_setclock) and using CLOCK_MONOTONIC in the deadline helper; the monotonic deadline also avoids being distorted across the wait loops.</violation>
</file>

<file name="Packages/macOS/CmuxTerminal/Tests/CmuxTerminalTests/TerminalSurfaceRuntimeTeardownCoordinatorTests.swift">

<violation number="1" location="Packages/macOS/CmuxTerminal/Tests/CmuxTerminalTests/TerminalSurfaceRuntimeTeardownCoordinatorTests.swift:174">
P2: The final freed-set assertion can race: later tickets complete when the coordinator calls completion.finish(), which is independent of the `Task { await recorder.record(bits) }` actor hops spawned inside each freeSurface. If those record tasks haven't run yet, `Set(recorder.freed)` may be partial, making this test flaky. Await the recorder before asserting, matching the sibling test which uses `recorder.waitForFreeCount(...)`.</violation>
</file>

<file name="Packages/macOS/CmuxTerminal/Tests/CmuxTerminalTests/TerminalSurfaceTeardownCallbackLifetimeTests.swift">

<violation number="1" location="Packages/macOS/CmuxTerminal/Tests/CmuxTerminalTests/TerminalSurfaceTeardownCallbackLifetimeTests.swift:44">
P2: This new test measures wall-clock latency (`clock.now - start < .seconds(1)`) around `ghostty_surface_free(unrelatedSurface)` to prove the call wasn't intercepted by the blocking gate. On a loaded CI runner a correctly non-blocked call could exceed 1 second and fail for the wrong reason, and the assertion tests a timing threshold rather than the logical invariant (the gate didn't intercept this surface). Consider using a completion signal with a deadline-bounded wait instead, matching the pattern already used in `teardownSurfaceKeepsMainActorResponsiveWhileNativeFreeIsBlocked` below.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

@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.

5 issues found across 9 files

You’re at about 94% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

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="Packages/macOS/CmuxTerminal/Tests/GhosttyRuntimeTestStubs/GhosttyRuntimeTestStubs.c">

<violation number="1" location="Packages/macOS/CmuxTerminal/Tests/GhosttyRuntimeTestStubs/GhosttyRuntimeTestStubs.c:48">
P3: The gate's 5s deadline is derived from CLOCK_REALTIME, which can be stepped forward or backward (NTP sync, manual time changes). A forward step makes pthread_cond_timedwait return ETIMEDOUT early and the gate flake as a false failure; a backward step can delay the watchdog well beyond 5s. Use CLOCK_MONOTONIC for a stable wait timeout; since the default-initialized condvar is CLOCK_REALTIME, initialize it via pthread_condattr_setclock(CLOCK_MONOTONIC) so the monotonic abs_timeout is valid.</violation>

<violation number="2" location="Packages/macOS/CmuxTerminal/Tests/GhosttyRuntimeTestStubs/GhosttyRuntimeTestStubs.c:72">
P3:</violation>
</file>

<file name="Packages/macOS/CmuxTerminal/Tests/CmuxTerminalTests/TerminalSurfaceRuntimeTeardownCoordinatorTests.swift">

<violation number="1" location="Packages/macOS/CmuxTerminal/Tests/CmuxTerminalTests/TerminalSurfaceRuntimeTeardownCoordinatorTests.swift:129">
P3: The new test duplicates the ~15-line stuck-free harness (AsyncStream<Void>.makeStream + DispatchSemaphore + makeAsyncIterator/next + defer cleanup) already used by stuckHibernationFreeDoesNotStrandAnotherAdmissionOrClose. Extracting a small helper (e.g. a 'stuckFree' that returns a StartedStream + release() cleans up) would remove the duplicated scaffolding and keep the teardown tests focused on their lane-specific assertions.</violation>
</file>

<file name="Packages/macOS/CmuxTerminal/Tests/CmuxTerminalTests/TerminalSurfaceTeardownCallbackLifetimeTests.swift">

<violation number="1" location="Packages/macOS/CmuxTerminal/Tests/CmuxTerminalTests/TerminalSurfaceTeardownCallbackLifetimeTests.swift:39">
P2: This new test measures wall-clock elapsed time (`ContinuousClock().now` diff under 1 second) to assert the surface-free gate didn't intercept an unrelated surface. Wall-clock latency thresholds are flaky under CI load — a correctly non-blocked call can still take over 1 second on a busy runner, failing the test for the wrong reason. Use a completion signal with a deadline-bounded wait instead (as done in `teardownSurfaceKeepsMainActorResponsiveWhileNativeFreeIsBlocked` right below), so the test asserts the actual invariant (the call didn't block on the gate) rather than a timing threshold.</violation>

<violation number="2" location="Packages/macOS/CmuxTerminal/Tests/CmuxTerminalTests/TerminalSurfaceTeardownCallbackLifetimeTests.swift:76">
P3: The `teardownSurfaceKeepsMainActorResponsiveWhileNativeFreeIsBlocked` test decides pass/fail on a fixed 1-second wall-clock window for the main-actor probe. A genuinely healthy teardown can be scheduled late under CI load, so this can flake false-fail even though the fix is correct. Consider making the timeout configurable/injected (or polling the probe with a deadline rather than a single 1s gate) so the watchdog bounds deadlock without coupling the pass/fail decision to absolute timing.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

@cursor

cursor Bot commented Aug 1, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@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.

Caution

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

⚠️ Outside diff range comments (1)
Packages/macOS/CmuxTerminal/Tests/CmuxTerminalTests/TerminalSurfaceRuntimeTeardownCoordinatorTests.swift (1)

274-274: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Make the test prove the bounded-close fallback.

The isolated lane is idle when the stale-reservation ticket runs. An incorrect implementation could still use that lane and pass this test. Start one blocked isolated teardown before enqueueing the stale ticket. Require the stale ticket and its free callback to complete while the isolated teardown remains blocked. Release the blocked teardown afterward.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Packages/macOS/CmuxTerminal/Tests/CmuxTerminalTests/TerminalSurfaceRuntimeTeardownCoordinatorTests.swift`
at line 274, Update staleIsolatedReservationFallsBackToBoundedClose to start and
block an isolated teardown before enqueueing the stale-reservation ticket,
ensuring the isolated lane is occupied. Await completion of both the stale
ticket and its free callback while the blocking teardown remains pending, then
release the blocked teardown afterward so the test proves the bounded-close
fallback rather than reuse of an idle isolated lane.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@Packages/macOS/CmuxTerminal/Tests/CmuxTerminalTests/TerminalSurfaceRuntimeTeardownCoordinatorTests.swift`:
- Line 274: Update staleIsolatedReservationFallsBackToBoundedClose to start and
block an isolated teardown before enqueueing the stale-reservation ticket,
ensuring the isolated lane is occupied. Await completion of both the stale
ticket and its free callback while the blocking teardown remains pending, then
release the blocked teardown afterward so the test proves the bounded-close
fallback rather than reuse of an idle isolated lane.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8b28b2b1-d94e-43c4-b12d-0eb18e8ac4f5

📥 Commits

Reviewing files that changed from the base of the PR and between e443d3a and cb37e0d.

📒 Files selected for processing (1)
  • Packages/macOS/CmuxTerminal/Tests/CmuxTerminalTests/TerminalSurfaceRuntimeTeardownCoordinatorTests.swift

…ick-hang

# Conflicts:
#	Packages/macOS/CmuxTerminal/Tests/GhosttyRuntimeTestStubs/include/GhosttyRuntimeTestStubs.h
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.

crash

1 participant