fix(tmux): TmuxSessionManager critical bugs (#3, #4, #14, #24)#131
Merged
fix(tmux): TmuxSessionManager critical bugs (#3, #4, #14, #24)#131
Conversation
…us routing, split tree search, paste escaping - Finding #3: Remove extra "%" prefix in paste-buffer command. focusedPaneId already contains the "%" prefix (e.g. "%0"), so "paste-buffer -t %\(paneId)" produced "paste-buffer -t %%0" which tmux rejects. - Finding #4: handleFocusedPaneChanged now calls setFocusedPane when the window matches focusedWindowId. Previously log-only, meaning pane focus changes from tmux (e.g. select-pane) did not route input to the correct pane via send-keys. - Finding #14: findSplitContainingWithSizeHelper now checks both children of a split, not just leftmostPaneId. The old code missed panes that were a right child or not the leftmost leaf in a subtree. - Finding #24: pasteTmuxBuffer now escapes "$" and backtick characters in clipboard content. These are interpreted by tmux as format variables and shell expansion respectively, causing injection in set-buffer.
There was a problem hiding this comment.
Pull request overview
This PR fixes 4 bugs in TmuxSessionManager.swift identified during a comprehensive code review (issue #129). It addresses a double % prefix in the paste-buffer command, a log-only handleFocusedPaneChanged handler that wasn't updating state, a split tree search that only checked the leftmost pane, and missing escaping for $ and backtick in clipboard content.
Changes:
- Fixed double
%prefix inpaste-buffer -tcommand wherefocusedPaneIdalready contains the%prefix - Updated
handleFocusedPaneChangedto callsetFocusedPanewhen the focused window matches, so input routing follows pane changes triggered by tmux - Rewrote
findSplitContainingWithSizeHelperto usecontains(paneId:)andisPane()on both children, correctly finding the innermost split for any pane position in the tree - Added escaping for
$and backtick characters inpasteTmuxBufferto prevent tmux format variable expansion and shell injection
The log message used "%\(paneId)" but paneId already contains the "%" prefix (e.g. "%0"), producing "%%0" in logs. The command on the previous line was already fixed, but the log line was missed.
In production, focusedPaneId always includes the "%" prefix (e.g. "%5"). Tests were passing bare numeric IDs (e.g. "5"), which masked the paste-buffer double-% bug fix from the previous commit. With the fix removing the extra % from the command string, the % must come from focusedPaneId itself.
- Fix comment: $ is not for tmux format variables (those use #{}).
Reworded to "defense-in-depth against potential expansion in edge cases"
- Add testPasteTmuxBufferEscapesDollarAndBacktick: verifies $ and
backtick are escaped in set-buffer command
- Add testHandleFocusedPaneChangedUpdatesFocusForMatchingWindow: sets
focusedWindowId to "@1", calls handleFocusedPaneChanged(windowId: 1,
paneId: 5), asserts focusedPaneId becomes "%5"
- Add testHandleFocusedPaneChangedIgnoresNonMatchingWindow: verifies
focusedPaneId unchanged when windowId does not match focusedWindowId
- Add setFocusedWindowIdForTesting() DEBUG helper for test access
Fix inconsistent indentation (9 spaces → 8) on XCTAssertFalse call. Add testSyncSplitRatioRightChildPaneSendsResizePane which validates finding #14: the right child of a horizontal split can be correctly found and resized. The old code only checked split.left.leftmostPaneId, missing right children entirely.
The old assertion (contains("$100") && !contains("\\$100")) was
always false because the escaped form is a substring of the unescaped
pattern. Use a negative lookbehind regex to properly detect any
unescaped dollar sign.
daiimus
pushed a commit
that referenced
this pull request
Mar 9, 2026
Address 6 low-severity findings from comprehensive code review: #35: Mark log-only notification handlers with MARK stub annotation #36: Extract surfaceView(from:) helper — replaces 25 identical call sites #37: Extract decodePayload(data:len:) helper — replaces 5 identical blocks #38: Add TmuxId format validation guards to selectPane, pasteTmuxBuffer, closeWindow, renameWindow, selectWindow #39: handleSessionRenamed now stores name in @published sessionName (moved out of stub section), reset on prepareForReattach #40: Define TmuxNotificationKey enum for typed notification userInfo keys — replaces 10 raw string keys across posting and consuming sides Also fixes double-% in paste-buffer command (same bug as S1 finding #3, independently fixed here since this branch predates PR #131). Closes #130
daiimus
pushed a commit
that referenced
this pull request
Mar 9, 2026
Address 6 low-severity findings from comprehensive code review:
closeWindow, renameWindow, selectWindow
(moved out of stub section), reset on prepareForReattach
— replaces 10 raw string keys across posting and consuming sides
Also fixes double-% in paste-buffer command (same bug as S1 finding #3,
independently fixed here since this branch predates PR #131).
Closes #130
daiimus
added a commit
that referenced
this pull request
Mar 9, 2026
* refactor(tmux): Swift code quality improvements (findings #35-40) Address 6 low-severity findings from comprehensive code review: closeWindow, renameWindow, selectWindow (moved out of stub section), reset on prepareForReattach — replaces 10 raw string keys across posting and consuming sides Also fixes double-% in paste-buffer command (same bug as S1 finding #3, independently fixed here since this branch predates PR #131). Closes #130 * fix(tmux): address Copilot round 2 review comments on PR #132 1. Move handleFocusedPaneChanged and handleSubscriptionChanged above the Stub Handlers MARK since they do real work (update state), not just log. 2. Clear sessionName in both controlModeExited() and cleanup() for consistency with other session metadata resets. 3. Add tests for window ID validation guards: closeWindow, renameWindow, and selectWindow all reject malformed IDs without sending commands. * fix(test): update stale doc comment for selectPane validation test --------- Co-authored-by: daiimus <daiimus@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes 4 bugs in
TmuxSessionManager.swiftidentified during comprehensive code review (issue #129).Changes
Finding #3: Double
%prefix in paste-buffer commandfocusedPaneIdalready contains the%prefix (e.g."%0"). Thepaste-buffer -t %\(paneId)call producedpaste-buffer -t %%0, which tmux rejects. Removed the extra%.Finding #4:
handleFocusedPaneChangedwas log-onlyWhen tmux notifies that the focused pane changed (e.g. via
select-pane), this handler now callssetFocusedPaneto update input routing. Without this, keystrokes continued going to the old pane viasend-keys.Finding #14:
findSplitContainingWithSizeHelperonly checked leftmost paneThe search for a pane's containing split only compared against
split.left.leftmostPaneId, missing panes that were right children or not the leftmost leaf. Now usescontains(paneId:)on both children, withisPanecheck to distinguish direct children from deeper subtree matches.Finding #24:
pasteTmuxBufferdidn't escape$or backtickAdded escaping for
$(tmux format variables) and`(shell expansion) in clipboard content before passing toset-buffer, preventing injection.Testing
./ci.sh buildpassesCloses #129
This PR was authored with AI assistance (Claude/OpenCode).