GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — fix(workspace/builder): Wait for shell prompt before layout and commands by tony · Pull Request #1018 · tmux-python/tmuxp

Conversation

@tony

@tony tony commented Mar 8, 2026

Copy link
Copy Markdown
Member

Summary

Fixes #365 (open since March 2018). WorkspaceBuilder sends SIGWINCH (via select_layout) and
commands (via send_keys) to panes before their shell finishes initializing. With zsh, this
produces the PROMPT_SP partial-line marker — an inverse+bold %.

Root cause traced through tmux and zsh source:

  • tmux screen_reinit() initializes new pane cursors at (0, 0) (screen.c:107-108).
    select_layout triggers ioctl(TIOCSWINSZ) on the pane PTY (window.c:449), delivering
    SIGWINCH to the child shell.
  • zsh preprompt() (utils.c:1530) runs the PROMPT_SP heuristic (utils.c:1545-1566)
    before every prompt, outputting the PROMPT_EOL_MARK (default %B%S%#%s%b). When SIGWINCH
    arrives mid-init, the interrupted redraw leaves the % marker visible.

Fix: Poll pane cursor position until it moves from origin (0, 0) — indicating the shell
has drawn its prompt — before calling select_layout. This ensures SIGWINCH only arrives
after the shell can handle resize gracefully.

Changes

  • Add _wait_for_pane_ready() that polls cursor_x/cursor_y via pane.refresh() (2s timeout, 50ms interval)
  • Call readiness check before select_layout in iter_create_panes() for all default-shell panes
  • Wait for blank panes too (- pane shorthand expands to shell_command: [])
  • Remove redundant select_layout call in build() that doubled SIGWINCH signals
  • Add 6 tests: readiness detection, timeout, parametrized call counts, layout deduplication

Test plan

  • uv run ruff check . --fix --show-fixes — clean
  • uv run ruff format . — unchanged
  • uv run mypy — no issues in 120 files
  • uv run py.test -vvv — 1027 passed, 2 skipped
  • Manual: tmuxp load projects with 12-window × 4-pane zsh config — no % markers

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
tony added 2 commits March 8, 2026 07:02
@tony
workspace/builder(fix[pane-readiness]): Wait for shell prompt before …
2b3eb13
…layout and commands

why: WorkspaceBuilder sends SIGWINCH (via select_layout) and commands
(via send_keys) to panes before their shell finishes initializing (#365,
open since 2018). With zsh, this triggers the PROMPT_SP partial-line
marker — an inverse+bold "%" — because:

  tmux: screen_reinit() sets cx=cy=0 on new panes (screen.c:107-108).
    select_layout → layout_fix_panes → window_pane_resize enqueues a
    resize, and window_pane_send_resize delivers SIGWINCH via
    ioctl(TIOCSWINSZ) on the pane's PTY (window.c:449).

  zsh: preprompt() (utils.c:1530) runs the PROMPT_SP heuristic
    (utils.c:1545-1566) before every prompt. It outputs the
    PROMPT_EOL_MARK (default: "%B%S%#%s%b") to detect partial lines,
    then pads with spaces and returns the cursor. When SIGWINCH arrives
    mid-init, the interrupted redraw leaves the marker visible.

what:
- Add _wait_for_pane_ready() that polls cursor position until it moves
  from origin (0,0), indicating the shell has drawn its prompt
- Call readiness check before select_layout in iter_create_panes() so
  SIGWINCH only arrives after the shell can handle resize gracefully
- Wait for all default-shell panes, not just those with commands (blank
  panes via "- pane" shorthand expand to shell_command: [])
- Remove redundant select_layout call in build() that doubled SIGWINCH
  signals after each pane yield
- Add tests for readiness detection, timeout, call count, and layout
  deduplication
@tony
docs(CHANGES) Pane-readiness fix for zsh partial-line marker
8db8479
@codecov

codecov Bot commented Mar 8, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.95652% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.32%. Comparing base (3785ebc) to head (9debb02).
⚠️ Report is 8 commits behind head on master.

Files with missing lines Patch % Lines
src/tmuxp/workspace/builder.py 82.35% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1018      +/-   ##
==========================================
+ Coverage   80.11%   80.32%   +0.21%     
==========================================
  Files          28       28              
  Lines        2409     2430      +21     
  Branches      457      462       +5     
==========================================
+ Hits         1930     1952      +22     
- Misses        356      357       +1     
+ Partials      123      121       -2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

tony
tony commented Mar 8, 2026
View reviewed changes
Comment thread src/tmuxp/workspace/builder.py
Comment on lines -326 to -328
if "layout" in window_config:
window.select_layout(window_config["layout"])

@tony tony Mar 8, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did some code archaeology on this because I had the same concern about reintroducing old layout/space bugs.

This specific select_layout() call looks like the redundant one, not the essential one. The builder still applies select_layout() per-pane in iter_create_panes(), which is the part that actually preserves
the incremental rebalance during pane creation. The call removed here was the second pass in build(), so it was effectively doubling the layout application after each yielded pane.

The historical “no space for new pane” regressions were more closely tied to session geometry than to this duplicate call. In 2022, #793 changed both layout timing and default-size, and the fallout in #800
was mainly from the server starting at 80x24 before a client attached. The later fix path was to size new_session() correctly (8a59203, then 0777c3b/63ac676d), not to keep a second select_layout()
here.

I also checked the current regressions: test_issue_800_default_size_many_windows still passes, and this branch adds test_select_layout_not_called_after_yield specifically to lock in the intended behavior:
call select_layout() once per pane, not twice. So I don’t think removing this duplicate call should reintroduce the lack-of-space issue.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@tony tony changed the title fix(workspace/builder): Wait for shell prompt before layout and commands (#365) fix(workspace/builder): Wait for shell prompt before layout and commands Mar 8, 2026
tony added 5 commits March 8, 2026 07:35
@tony
workspace/builder(fix[logging]): Preserve exception traceback in read…
f669002
…iness check

why: The except block in _wait_for_pane_ready discarded the exception
traceback, making it impossible to diagnose pane refresh failures.
what:
- Add exc_info=True to logger.debug call in the except block
@tony
tests/workspace/builder(test[pane-readiness]): Add window_shell readi…
0139018
…ness skip coverage

why: The window_config.get("window_shell") fallback path at builder.py:566
was untested. Only pane-level shell: had fixture coverage.
what:
- Add skips_all_panes_with_window_shell fixture to PANE_READINESS_FIXTURES
- Verifies expected_wait_count=0 when window_shell is set
@tony
workspace/builder(docs[pane-readiness]): Document shell-skip heuristi…
d3c0c87
…c scope

why: The readiness skip for panes with shell/window_shell was undocumented,
making it unclear to future maintainers why pane_shell is None is the check.
what:
- Add comment explaining that shell/window_shell runs a command launcher,
  not an interactive shell, so there is no prompt to wait for
@tony
workspace/builder(fix[doctest]): Increase readiness doctest timeout t…
818466e
…o 5s

why: The 2s timeout was too tight for slow environments, causing
flaky doctest failures when the shell hadn't drawn its prompt yet.
what:
- Bump _wait_for_pane_ready doctest timeout from 2.0 to 5.0 seconds
@tony
conftest(fix[doctest]): Add rerun markers for tmux-dependent doctests
9debb02
why: Tmux doctests are inherently timing-sensitive — shell startup
races can cause spurious failures even with generous timeouts.
what:
- Add pytest_collection_modifyitems hook to apply flaky(reruns=2)
  marker to DoctestItems from DOCTEST_NEEDS_TMUX modules
@tony
tony merged commit 1ac73ea into master Mar 8, 2026
13 checks passed

Uh oh!

There was an error while loading. Please reload this page.

@tony
tony deleted the pane-readiness branch March 8, 2026 13:10
@tony tony mentioned this pull request Jun 25, 2026
Wait for a window's panes concurrently to speed up loads #1056
Open
18 tasks done
tony added a commit that referenced this pull request Jun 27, 2026
@tony
builder(perf[readiness]) parallelize prompt wait
38312de
why: PR #1018 waits for each pane's shell to draw its prompt before
the next pane is created, so workspace load time grew linearly with
pane count (issue #1053: 12 windows / 18 panes took ~25s). The shells
already initialize concurrently once tmux spawns them; only the
observation was serial.

what:
- Add _wait_for_panes_ready(), a concurrent readiness barrier that
  polls every pane under one shared timeout instead of one per pane.
- Restructure build() into phases: create all windows and panes, wait
  for every default-shell pane once, then lay out and send commands.
- Create panes by splitting the newest pane only and defer the layout
  until after the barrier, so no pane is resized mid-prompt — keeping
  the zsh partial-line '%' fix (issue #365) intact.
- Add _split_pane_reclaiming_space(): on a no-space split failure,
  wait for existing panes then select_layout and retry.
- Keep iter_create_panes() as a per-window back-compat wrapper.
- Reduce _wait_for_pane_ready() to a single-pane delegate.
- Update readiness/layout tests for the barrier; add tests for the
  shared barrier, whole-session wait, and layout-after-barrier order.
tony added a commit that referenced this pull request Jun 27, 2026
@tony
builder(perf[readiness]) parallelize prompt wait
cab36d5
why: PR #1018 waits for each pane's shell to draw its prompt before
the next pane is created, so workspace load time grew linearly with
pane count (issue #1053: 12 windows / 18 panes took ~25s). The shells
already initialize concurrently once tmux spawns them; only the
observation was serial.

what:
- Add _wait_for_panes_ready(), a concurrent readiness barrier that
  polls every pane under one shared timeout instead of one per pane.
- Restructure build() into phases: create all windows and panes, wait
  for every default-shell pane once, then lay out and send commands.
- Create panes by splitting the newest pane only and defer the layout
  until after the barrier, so no pane is resized mid-prompt — keeping
  the zsh partial-line '%' fix (issue #365) intact.
- Add _split_pane_reclaiming_space(): on a no-space split failure,
  wait for existing panes then select_layout and retry.
- Keep iter_create_panes() as a per-window back-compat wrapper.
- Reduce _wait_for_pane_ready() to a single-pane delegate.
- Update readiness/layout tests for the barrier; add tests for the
  shared barrier, whole-session wait, and layout-after-barrier order.
@tony tony mentioned this pull request Jun 27, 2026
Very slow performance opening layouts/workspaces #1053
Open
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

No reviews

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Outputs % / percent sign after every command in some panes

Uh oh!

There was an error while loading. Please reload this page.

1 participant

@tony
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.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.