Skip to content

remote-tmux: re-read a pane from tmux when its width changes, not only when it grows - #17140

Merged
teamleaderleo merged 3 commits into
manaflow-ai:mainfrom
ejc3:fix/remote-tmux-repaint-on-narrow
Oct 3, 2026
Merged

teamleaderleo merged 3 commits into
manaflow-ai:mainfrom
ejc3:fix/remote-tmux-repaint-on-narrow

Conversation

@ejc3

@ejc3 ejc3 commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Attach cmux ssh-tmux to a session whose pane is wider than the mirror, and the mirror's rows end up at different heights from the pane's. The prompt and the cursor sit a few rows away from where tmux has them, until something redraws the screen. The same happens whenever a mirrored shell pane is made narrower.

To reproduce: start a detached session at 120x40, press Enter a few times at a zsh prompt, then attach from a window that gives the pane 93 columns. In my run the mirror's rows were four off.

tmux rewraps a pane's lines when its width changes in either direction. The mirror rewraps its own copy, and that matches only for lines it saw arrive as live output. Rows that came from the initial capture carry no wrap information, and the capture drops the trailing spaces zsh pads its prompt line with, so after a narrowing the two wrap at different places. cmux already re-reads tmux's screen with capture-pane after a pane grows. This makes it do the same after any width change. A pane that only got shorter is left alone, because tmux moves whole rows into history there and so does the mirror.

Testing

Two tests in RemoteTmuxPaneSeedTransportTests, run with xcodebuild test-without-building -scheme cmux-unit -only-testing:cmuxTests/RemoteTmuxPaneSeedTransportTests:

  • 53163d0, the tests alone: narrowedPaneIsRepaintedFromTmux fails, no repaint is queued. paneThatOnlyGotShorterIsNotRepainted passes before and after.
  • 924ae5b, with the fix: both pass, along with the rest of the suite and four other remote tmux suites (76 tests in 5 suites).

main at 68bb2f1 does not compile its test target: RightSidebarTabCustomizationTests and CloudMachineOrderingTests fail to build. Both runs left those two files out locally. That change is not in this branch.

I also ran it against a live tmux server on a local build that carries this change on top of other branches of mine. A script creates a 120x40 session, presses Enter six times, attaches, waits for the pane size to hold still, and compares the mirror's screen with capture-pane. Without the fix the rows differed. With it they matched, and ten other attach, resize and reconnect cases still passed.

Changelog

Fixed: A remote tmux mirror no longer shows its rows and cursor out of place after a pane is made narrower.

Checklist

  • Behavior changes have added or updated tests, or Testing says why not

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Fixes the remote tmux mirror showing rows and the cursor out of place after a pane is made narrower.

tmux rewraps a pane when its width changes in either direction, but the mirror only re-read tmux's screen on growth, so after a narrowing it kept its own rewrap. That rewrap mismatched for seeded rows and for prompt lines padded with spaces, leaving rows and the cursor off until something redrew the screen. cmux now repaints the pane from tmux after any width change; a pane that only got shorter is still left alone, since tmux moves whole rows into history there.

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

Review in cubic

Summary by CodeRabbit

  • Bug Fixes
    • Improved pane refresh behavior after tmux-driven layout changes. Existing panes are refreshed when their width changes in either direction or their height increases, helping content display correctly after resizing.
    • Avoided unnecessary refreshes when a pane’s height decreases without a width change.

ejc3 added 2 commits October 3, 2026 02:17
tmux rewraps a pane when its width changes either way, but the mirror re-reads
tmux's screen only when a pane grows. After a narrowing it keeps its local
rewrap, which differs for seeded rows and for prompt lines padded with spaces.
…y when it grows

tmux rewraps a pane on any width change. The mirror's own rewrap matches only
for lines it saw arrive live: seeded rows carry no wrap information, and
capture drops the trailing spaces a shell pads its prompt line with. After a
narrowing the two wrapped at different places, so rows and cursor sat off
until something redrew them.

The repaint that already follows a grow now also follows any width change.
@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@coderabbitai

coderabbitai Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

🧰 Additional context used
📚 Code guidelines (3)
.github/review-bot-rules/test-determinism.md — configured
.github/review-bot-rules/swift-architectural-rethink.md — configured
.github/review-bot-rules/source-control-artifacts.md — configured

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: manaflow-ai/cmux/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 491a94c1-7662-4086-8414-d01e24fee44a
📥 Commits

Reviewing files that changed from the base of the PR and between 924ae5b and c9da55a.

📒 Files selected for processing (1)
  • cmuxTests/RemoteTmuxPaneSeedTransportTests.swift

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 2 remain after this review.


📝 Walkthrough

Walkthrough

The tmux pane repaint method now selects existing panes when their width changes or their height increases. Layout publication calls the renamed method. Tests cover width narrowing, height-only shrinking, grid growth, and reconnect recovery.

Changes

Pane redraw

Layer / File(s) Summary
Select panes for tmux redraw
Sources/RemoteTmuxControlConnection+PaneSeed.swift, Sources/RemoteTmuxControlConnection+LayoutPublication.swift, cmuxTests/RemoteTmuxPaneSeedTransportTests.swift
The repaint method selects existing panes when width changes in either direction or height increases. Layout publication calls the renamed method. Tests cover width narrowing, height-only shrinking, grid growth, and reconnect recovery.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Suggested reviewers: austinywang

Merge Risk: ⚪ Minimal · up to c9da5

No merge-blocking risk is identified in the reviewed change.

🚥 Pre-merge checks | ✅ 24 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.55% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 11 functions across 3 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (24 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: re-reading a remote tmux pane when its width changes, including when it narrows.
Description check ✅ Passed The description includes the required Summary, Testing, and Changelog sections. It explains the problem, reports test results and limitations, and confirms behavior with a live tmux test. The Demo Vid…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Cloud Persistent Session And Early Input ✅ Passed The diff changes only remote-tmux pane repaint behavior and its tests. It adds capture-pane repainting after pane-width changes and does not change Cloud terminal creation, persistent transport, manua…
Cmux Swift Actor Isolation ✅ Passed The production changes do not introduce a Swift actor-isolation mistake. RemoteTmuxControlConnection is explicitly @MainActor, so the renamed repaint method and its pane-state operations remain ma…
Cmux Swift Blocking Runtime ✅ Passed The production diff adds no blocking waits, sleeps, delayed dispatch, polling, synchronous main-queue calls, timers, or manual locks. It changes the repaint condition to include width changes and call…
Cmux Browser Automation Off-Main ✅ Passed The check is not applicable to this pull request. The authoritative diff changes only remote-tmux layout publication, pane repaint logic, and its tests. It does not change browser socket commands, `Te…
Cmux Expensive Synchronous Load ✅ Passed The PR does not add an expensive synchronous agent-history load. Its production changes expand an existing pane repaint after layout publication to include width changes. The repaint queues tmux comma…
Cmux Cache Substitution Correctness ✅ Passed The diff does not replace a fresh read with a cache. It expands when the mirror requests capture-pane; that command reads the current screen from tmux and remains the authoritative repair. The previ…
Cmux No Hacky Sleeps ✅ Passed This check applies only to production changes in TypeScript, JavaScript, shell, or build/runtime scripts. The pull request changes two Swift production files and one Swift test file. It introduces no …
Cmux Algorithmic Complexity ✅ Passed The diff adds no nested collection scan or per-target rescan. The repaint selection traverses currentLeaves once and performs dictionary lookups in previousLeaves. The .sorted() call was already…
Cmux Swift Concurrency ✅ Passed The diff does not introduce or materially expand a legacy Swift concurrency pattern. It renames the existing synchronous repaint selector and broadens its width-change condition. The repaint uses the …
Cmux Swift @Concurrent ✅ Passed The diff adds no async, nonisolated, @concurrent, or actor-isolation annotations. The changed repaint helper is synchronous, and RemoteTmuxControlConnection is @MainActor-isolated. It calls …
Cmux Swift Package Boundaries ✅ Passed The diff extends an existing app-level tmux connection workflow. It changes the pane-selection condition from width growth to any width change, then calls the existing repaintPaneVisibleScreen path.…
Cmux Swiftpm Lockfiles ✅ Passed PASS. The PR changes only two Swift source files and one Swift test file. It does not change a SwiftPM package, Xcode project, .gitignore, workflow, or dependency declaration, so the SwiftPM lockfil…
Cmux Swift Logging ✅ Passed The diff adds no logging statements or diagnostic output. It renames the repaint helper and broadens its width-change condition. This can trigger the existing capture path more often, but that path’s …
Cmux User-Facing Error Privacy ✅ Passed The diff changes pane-repaint selection and updates comments and tests. A width change now invokes the existing repaintPaneVisibleScreen capture flow; the patch adds no user-facing error, alert, com…
Cmux Full Internationalization ✅ Passed The PR changes pane repaint scheduling and developer-only Swift comments. Its only new user-facing behavior is to refresh the pane screen from tmux after a width change; it adds or changes no user-fac…
Cmux Swiftui State Layout ✅ Passed The diff changes tmux pane-layout publication and repaint logic, plus related tests. The production files import CmuxRemoteSession and Foundation, not SwiftUI. The changed code adds no ObservableObjec…
Cmux Architecture Rethink ✅ Passed The diff makes a small correctness change in the existing tmux layout-publication path. It uses the existing repaintPaneVisibleScreen action when a verified pane width changes in either direction or…
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed The pull request changes tmux pane repaint behavior and related tests. The diff adds or changes no user-visible NSWindow, NSPanel, NSWindowController, SwiftUI Window, or WindowGroup code. The NSWindow…
Cmux Source Artifacts ✅ Passed The diff changes only two hand-written Swift source files and one Swift test file. These are intentional product and test changes. No generated output, scratch directory, dependency checkout, cache, b…
Cmux No Test Or Debug Seam In Production Source ✅ Passed The production-source changes add no test/debug seam. repaintPanesThatGrew was renamed to repaintPanesTmuxRedrew without widening visibility, and handlePaneRectsReply calls it as part of product…
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

CI failure attribution

CI failed on c9da55a912 (run 37155140626 attempt 2): 1 code, 1 unknown.

Job Verdict Why
Fast static checks code a static check failed
guards / workflow-guard-tests / ci unknown no known signature; failed step: Propagate failed independent fast guard
Matched log lines
Fast static checks: FAILED localization (1.22s)

Not re-run automatically: Fast static checks, guards / workflow-guard-tests / ci are not machine failures.

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); signatures are its SIGNATURES table. A machine verdict is the runner's fault, not this PR's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

CI fast guards failed on c9da55a912 (https://github.com/manaflow-ai/cmux/actions/runs/37155140303). It does not block the merge; a red guard merged into main breaks it for every open PR.

Validate macOS jobs select a pinned Xcode (red on main too, not this PR)

Main has failed this step since #17206 by @teamleaderleo (self-merged) (#17169). Merge main again once the fix lands there.

Validate owned Mac build state (red on main too, not this PR)

Main has failed this step since #17168 by @teamleaderleo (self-merged) (#17169). Merge main again once the fix lands there.

Validate fork runner routing (red on main too, not this PR)

Main has failed this step since #17206 by @teamleaderleo (self-merged) (#17169). Merge main again once the fix lands there.

Run canonical CMUX CI guard profile (red on main too, not this PR)

Main has failed this step since #17206 by @teamleaderleo (self-merged) (#17169). Merge main again once the fix lands there.

Agents: python3 scripts/ci/guard_attribution.py fix applies the mechanical fixes locally. This comment is updated in place on each push.

@teamleaderleo
teamleaderleo merged commit e88d636 into manaflow-ai:main Oct 3, 2026
60 of 67 checks passed
@teamleaderleo

Copy link
Copy Markdown
Collaborator

Merged, thank you @ejc3! :D

@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

Merge receipt for c9da55a912, merged 2026-10-03 21:41:56 UTC

  • Not verified at merge: ci-status (failure), CI fast guards (failure), Fast static checks (failure), guards (18) (failure), linux-preflight (failure), tests (failure)
  • Verified: backend migrations applied, CI timing, GhosttyKit release check, plan, Web complexity, web-validation
  • Skipped by policy: apply-production, apply-staging, browser, Claude wrapper regressions, Dogfood build #​${{ github.event.pull_request.number }}, full-suite-coverage, macos, macOS admission gate, remote-daemon, suite-coverage, ui-tests, web, and 3 more
  • Full suite: runs on main after merge.

Labeled merged-unverified: if main breaks near this merge, look here first.

@github-actions github-actions Bot added the merged-unverified A judging check was not green at merge; see the merge receipt comment label Oct 3, 2026
rustybret pushed a commit to rustybret/bmux that referenced this pull request Oct 3, 2026
00f182f Set Claude idle after reentrant stop without work (manaflow-ai#16635)
d8ef7e7 Reject unknown and valueless options in cmux hooks setup (manaflow-ai#17183)
46b5f9c remote-tmux: let a failed socket request say what failed (manaflow-ai#17134)
e88d636 remote-tmux: re-read a pane from tmux when its width changes, not only when it grows (manaflow-ai#17140)
984baea remote-tmux: close a window emptied by gathering its mirrors into a new one (manaflow-ai#17141)
9c41d59 fix: recover hidden terminal renderer after window attach (manaflow-ai#16548)
70854a5 ci: publish dogfood artifacts from red CI runs (manaflow-ai#17210)
db77e58 fix(cli): reject missing notify text values (manaflow-ai#16778)
76ccbfc ci: activate org-member dogfood artifact publisher (manaflow-ai#17206)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merged-unverified A judging check was not green at merge; see the merge receipt comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants