wait_for_text: two-call state/capture race within a single poll tick
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 5/5
- Tempo stimato
- Più di una settimana
- Idoneità per principianti
- 35/100
Direzione di ricerca
Start at the wait_for_text entry point and trace _read_pane_state through capture_pane, then compare the behavior with wait_for_channel in src/libtmux_mcp/tools/wait_for_tools.py. Use a stress-test fixture or telemetry to determine whether single-tick misses occur often enough to justify a change; otherwise document the current behavior and retain the rollover guard.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Type: architecture · Tier: deferred · Tool: wait_for_text
What's happening
Each poll in wait_for_text runs two tmux subprocess calls in sequence:
_read_pane_stateissuesdisplay-messageto readhistory_size,cursor_y,pane_height,pane_pid,pane_dead.pane.capture_pane(start=start_line, end=None, join_wrapped=True)issuescapture-pane, wherestart_line = baseline_abs - state.history_size + 1.
Between (1) and (2), tmux can scroll more lines into history. tmux's capture-pane computes top = gd->hsize + n against the live hsize at capture time (cmd-capture-pane.c#L158), not the hsize we sampled in step 1. So when N new rows scroll between the two calls:
- We pass
n = baseline_abs - hsize_at_step1 + 1 - tmux computes
top = hsize_at_step2 + n = baseline_abs + 1 + (hsize_at_step2 - hsize_at_step1) - The captured window starts N rows past the row we wanted; those N rows are invisible to the wait this tick.
When it matters
Single-tick latency under bursty output. The next poll usually picks the missed rows back up — unless the missed rows have already scrolled past the visible region and been collected by grid_collect_history, at which point the rollover guard fires and the wait raises. So the bug surface is:
- One-tick
intervalof latency on transient bursts (default 50 ms; bounded). - Permanent miss only at the moment of history rollover — but rollover now raises.
In other words: the race exists but its impact is bounded by interval and capped at "raise" rather than "silently wrong" thanks to the rollover guard.
Options under consideration
1. Re-read after capture, retry on drift
state_pre = await asyncio.to_thread(_read_pane_state, pane)
start_line = baseline_abs - state_pre.history_size + 1
lines = await asyncio.to_thread(pane.capture_pane, start=start_line, ..., join_wrapped=True)
state_post = await asyncio.to_thread(_read_pane_state, pane)
delta = state_post.history_size - state_pre.history_size
if delta > 0:
# capture started \`delta\` rows too late; re-issue with adjusted start
...
Doubles per-tick subprocess cost in the worst case (3 tmux calls instead of 2 when drift is detected). Complicates the _PaneState invariant set: now we track two state reads per tick. Test matrix grows.
2. Chain in a single tmux command
Build one pane.cmd(...) invocation that issues display-message ; capture-pane with tmux's \; chaining. One stdout stream needs to be split by the caller. Drops out of libtmux's typed API. Tightly couples to tmux's chaining quirks.
3. Document, rely on next-tick recovery (current behavior)
Acceptable because:
- The miss is bounded by
interval(default 50 ms). - Permanent misses now raise rather than silently return wrong results, courtesy of the rollover guard.
- The deterministic alternative for command-completion synchronization is
wait_for_channelcomposed withtmux wait-for -S— zero polling, zero races.
Recommendation
Stay on option 3 until real-world telemetry shows flaky single-tick misses. The blast radius is small and the agent-facing escape hatch (wait_for_channel) is already documented in the wait_for_text "When NOT to use this" section. Re-evaluate if a stress-test fixture starts catching missed transitions.
References
- tmux capture-pane start math: cmd-capture-pane.c#L158 (
top = gd->hsize + n) - tmux history collection: grid.c#L386 (
grid_collect_history) - tmux command queue / chaining: cmd-queue.c
- Lingua principale
- Python
- Stelle
- 13
- Fork
- 0
- Merge medio
- 22m
- PR unite (30g)
- 4
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Nessun modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di tmux-python/libtmux-mcp
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
tmux-python/libtmux-mcp#121 ·
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
tmux-python/libtmux-mcp#120 ·
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
tmux-python/libtmux-mcp#119 ·
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
tmux-python/libtmux-mcp#118 ·
-
pytest filterwarnings suppresses the DeprecationWarnings this project needs to seeForse già presa @tony l’ha presa 88 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
tmux-python/libtmux-mcp#111 ·
Tutte le issue di tmux-python/libtmux-mcp
Issue simili
-
follow-up
Difficoltà 2/5 1-3 ore Idoneità per principianti 62/100
I maintainer di solito rispondono entro 1 giorno
-
Download helper still uses huggingface_hub arguments removed in 1.0: USE_SYMLINKS is silently ignored and every download ends with a WinError 3 lineForse già presa @li-lizhe l’ha presa oggi. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
-
IPv6 node addresses produce an invalid request URLForse già presa @RaphaelFakhri l’ha presa oggi. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
typesense/typesense-python#154 ·
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
elastic/elasticsearch-py#3592 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 77/100
developmentseed/titiler-cmr#250 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno