docs(automation_patterns): the wait helpers never wait — the marker matches the command echo

Open
#706 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
72/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Quiet
Tech stack
python
Domain
documentation

Research direction

Start with docs/topics/automation_patterns.md and review the wait_for_output and capture_after_marker examples, then read Server.wait_for() in src/libtmux/server.py. Run the page's doctests or the automation_patterns.md[4] check and add assertions covering delayed output, timeout, and captured command output. Done means the documented helpers no longer return on the echoed command and the examples reliably pass.

Written by the indexing model from the issue text.

Description

bug

Bug

Both wait helpers taught in docs/topics/automation_patterns.md return before the command they are waiting on has produced any output, because the shell echoes the command line and the marker is matched in that echo.

Same root cause as #654, but in the documentation — so it is the pattern readers copy into their own code.

The two helpers fail differently, and the quieter one is the worse one:

helper failure
wait_for_output always returns True immediately. timeout and poll_interval are dead parameters. Its doctest passes vacuously — it would pass if the command never ran at all.
capture_after_marker its poll loop exits on the first capture, so timeout=2.0 never waits; the only real wait is a bare time.sleep(0.3). Visibly flaky — automation_patterns.md[4] fails intermittently.

Root cause

send-keys types the command into the pane, and the shell echoes it back before running it. The echoed line contains the marker string, so a substring match over capture_pane() succeeds instantly.

docs/topics/automation_patterns.md#L137-L139:

>>> monitor_pane.send_keys('sleep 0.2; echo "READY"')
>>> wait_for_output(monitor_pane, 'READY', timeout=2.0)
True

READY is in sleep 0.2; echo "READY" — the command itself. The helper matches the echo, not the output.

Reproduction

The helper verbatim from the page, with the sleep raised so the race is unmissable:

pane.send_keys('sleep 2.0; echo "READY"')
got = wait_for_output(pane, "READY", timeout=5.0)   # helper copied from the docs

Observed:

wait_for_output(...) returned True after 0.02s
the command needs >= 2.0s to print READY
screen at that moment:
   'sleep 2.0; echo "READY"'      <- only the echo; no output yet

Observed: True in 0.02 s.
Expected: blocks ~2 s, then True — or False on timeout.

capture_after_marker fails the same way: its first captured line containing MARKER is index 0, the echoed command.

Suggested fix

Teach tmux wait-for rather than polling for a string. libtmux already wraps it — Server.wait_for() — and it is race-free by construction: no marker, no poll interval, no sleep.

pane.send_keys('sleep 2.0; echo "captured data"; tmux wait-for -S done')
server.wait_for('done')            # blocks until the command actually finished
result = pane.capture_pane()

There is no lost-wakeup race even if the command finishes before the wait is issued. From tmux's cmd-wait-for.c:

/* signal: nobody waiting yet -> remember it */
if (TAILQ_EMPTY(&wc->waiters) && !wc->woken) { wc->woken = 1; return (CMD_RETURN_NORMAL); }

/* wait: already woken -> return at once */
if (wc->woken) { cmd_wait_for_remove(wc); return (CMD_RETURN_NORMAL); }

Inside a pane $TMUX is already set, so a bare tmux wait-for -S done reaches the right server with no extra wiring.

If a string-matching example is still wanted — reasonable for output you do not control — it must not be matchable against the echo. Either match per line and skip the echoed command (the fix proposed in #654), or use a sentinel that cannot appear in the command line, e.g. assemble it at runtime so the literal is never typed.

Tests

The page's doctests currently prove nothing, so they need to become real assertions:

  1. wait_for_output against a command that sleeps longer than the poll interval must take at least that long to return — that fails today.
  2. wait_for_output must return False on timeout when the text never appears (today it cannot, since it matches the echo).
  3. capture_after_marker must return the command's output, not the tail of the echoed line.

Related

  • #654 — same root cause, in a test rather than the docs
  • #484, #456 — adjacent capture_pane reliability

Environment

  • libtmux: v0.61.0 (both helpers unchanged since the tag)
  • tmux: 3.7b
  • Python: 3.14
Dominant language
Python
Stars
1.2k
Forks
127
Avg merge
2h 13m
Merged PRs (30d)
1

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from tmux-python/libtmux

All issues in tmux-python/libtmux

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.