Skip to content

search_panes: visual-row capture misses wrap-spanning patterns #55

@tony

Description

@tony

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:

  1. Slow path (Python-regex over captured lines): the mechanical fix is join_wrapped=True. Drop-in.
  2. 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

  1. 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.
  2. 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.
  3. Test: regression covering a wrap-spanning pattern, parallel to test_wait_for_text_matches_pattern_across_wrap.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions