Problem
search_panes doesn't join visually-wrapped lines before matching, so plain-text patterns that tmux wraps at the pane's column width split across two captured rows and miss the match. Long error messages (e.g. "Build failed: module not found in /very/long/path/to/module.py") are the canonical example — they wrap mid-string and re.search against each row in isolation never sees the full string.
wait_for_text shipped the wrap fix in #45 (commit 67eed15) by passing join_wrapped=True to pane.capture_pane() — which adds tmux's -J flag. search_panes was not part of that sweep.
Repro
Render a pane with a long line that wraps:
$ pytest tests/integration/test_pipeline_with_a_very_long_test_class_name_to_force_wrap.py::TestXYZ::test_specific_assertion_that_fails
FAILED
If the pane is ~80 cols wide, the path wraps. Then:
search_panes(pattern="test_specific_assertion_that_fails")
Returns the pane (the substring lives on row 2). But:
search_panes(pattern="tests/integration/test_pipeline_with_a_very_long_test_class_name_to_force_wrap.py::TestXYZ::test_specific_assertion_that_fails")
Returns no match — the pattern spans the wrap boundary.
Root cause
src/libtmux_mcp/tools/pane_tools/search.py slow path:
lines = pane.capture_pane(start=content_start, end=content_end)
matched_lines = [line for line in lines if compiled.search(line)]
No join_wrapped=True. Compare to src/libtmux_mcp/tools/pane_tools/wait.py:
lines = await asyncio.to_thread(
pane.capture_pane,
start=start_line,
end=None,
join_wrapped=True,
)
Nuance — the fast path is structurally wrap-blind
search_panes has two paths:
- Slow path (Python-regex over captured lines): the mechanical fix is
join_wrapped=True. Drop-in.
- Fast path (
#{C:pattern} format conditional): runs tmux's C-level window_pane_search() against the grid by visual row. There's no -J analog at the format-conditional level, so this path is wrap-blind by tmux design.
The fast path triggers when (a) no scrollback range is requested and (b) the pattern has no regex metacharacters or tmux format-injection characters. Long plain-text error messages from build output hit the fast path most of the time — exactly the worst case for wrap-blindness.
Proposed fix
- Slow path: add
join_wrapped=True to the pane.capture_pane(...) call. Same trade-off wait_for_text accepts — matched_lines may contain strings longer than pane_width.
- Fast path: docstring note acknowledging that plain-text patterns spanning the pane's wrap column may miss matches; suggest passing
regex=True or supplying scrollback range (content_start=-N) to force the slow path.
- Test: regression covering a wrap-spanning pattern, parallel to
test_wait_for_text_matches_pattern_across_wrap.
- CHANGES entry under
### Fixes (Published-Release Test passes — search_panes shipped in 0.1.0a2).
Out of scope
- Routing wrap-spanning patterns from the fast path to the slow path automatically. Requires tmux-side detection of pane width vs. pattern length, which is heuristic and adds complexity.
- Extending
-J to tmux's #{C:...} format conditional. That's an upstream-tmux change.
Problem
search_panesdoesn't join visually-wrapped lines before matching, so plain-text patterns that tmux wraps at the pane's column width split across two captured rows and miss the match. Long error messages (e.g."Build failed: module not found in /very/long/path/to/module.py") are the canonical example — they wrap mid-string andre.searchagainst each row in isolation never sees the full string.wait_for_textshipped the wrap fix in #45 (commit67eed15) by passingjoin_wrapped=Truetopane.capture_pane()— which adds tmux's-Jflag.search_paneswas not part of that sweep.Repro
Render a pane with a long line that wraps:
If the pane is ~80 cols wide, the path wraps. Then:
Returns the pane (the substring lives on row 2). But:
Returns no match — the pattern spans the wrap boundary.
Root cause
src/libtmux_mcp/tools/pane_tools/search.pyslow path:No
join_wrapped=True. Compare tosrc/libtmux_mcp/tools/pane_tools/wait.py:Nuance — the fast path is structurally wrap-blind
search_paneshas two paths:join_wrapped=True. Drop-in.#{C:pattern}format conditional): runs tmux's C-levelwindow_pane_search()against the grid by visual row. There's no-Janalog at the format-conditional level, so this path is wrap-blind by tmux design.The fast path triggers when (a) no scrollback range is requested and (b) the pattern has no regex metacharacters or tmux format-injection characters. Long plain-text error messages from build output hit the fast path most of the time — exactly the worst case for wrap-blindness.
Proposed fix
join_wrapped=Trueto thepane.capture_pane(...)call. Same trade-offwait_for_textaccepts —matched_linesmay contain strings longer thanpane_width.regex=Trueor supplying scrollback range (content_start=-N) to force the slow path.test_wait_for_text_matches_pattern_across_wrap.### Fixes(Published-Release Test passes —search_panesshipped in0.1.0a2).Out of scope
-Jto tmux's#{C:...}format conditional. That's an upstream-tmux change.