A window sliding back to its column after a drop can stop partway and stay there#377

opened2d agobySSamanyu Das

Found while checking the #314 fix live on 8 November 2026. A tiled window that slides back to its column after a drop can stop partway and stay there until the next layout change.

Steps to reproduce

  1. Run Miri Developer with T3 Code tiled in a 1676 by 1394 column on the 27-inch display.
  2. Keep the Mac busy, for example with Rust builds and a VM running (load average 15 to 50).
  3. Drag T3 Code by its title bar below the bottom edge of the screen and release.
  4. Repeat. It happened in about 1 of 20 drops.

A tool posted the drag, and a recorder read window frames every 5 ms (verify-miri run 20261108-trace2, drop 12). Runs 20261108-reassert2 (drops 14 and 18, Magnet not running) and 20261108-reassert3 (drop 28, load average about 5) showed the same stop without trace lines.

Expected result

The window slides all the way back to its slot in under a second, even when the app answers slowly.

Actual result

In drop 12 the window stopped at x 744, y 314, about 280 pt below its slot, and stayed there for the whole 8.5 s recording and until the next drop. The window stayed reachable on its display. Engine trace lines show the engine's writes during the move:

text
1+0.005 write to=IRect { min: (642, 626), max: (1504, 2028) } ax size err=0, ax position err=0
2+0.142 write to=IRect { min: (740, 310), max: (1602, 1712) } ax position (744, 314) err=0 at +0.331
3+0.610 observe frame=(740, 310) moving=true reposition=Some((826, 34)) outcome=Moved
4 (no further write for this window)

No write failed. The window still had its move marker towards the slot (826, 34), but the engine sent no more frames for it. The engine moved a neighbouring window during the same second. The cause is not confirmed. One guess is the row pacing in vendor/paneru/src/ecs/pacing.rs (pace_rows), which holds a window behind its neighbours using frames read on a sampler thread.

Acceptance criteria

  • An engine test reproduces a slide that stops partway after a drop, fails before the fix, and passes after it (make engine-test).
  • Live, with verify-miri and the live-test lock: in 40 drops of T3 Code below the screen's bottom edge, with the load average above 10, every drop ends at the window's slot, read by the frame recorder.

Miri version or commit

fix/issue-314 at c920dee5, engine 8011f0e with trace lines (paneru#141 adds a retry on top that does not touch this path)

macOS version and Mac model

macOS 27.0.1 (26A434), MacBook Air M3 (Mac15,12)

Displays and arrangement

27-inch external above the built-in display, separate Spaces on

Dependency and status (coordination, 10 Nov 2026)

  • Builds on another thing? No required dependency on #388, #386, #350, #333, or #376. Related observations from the same drop sessions appear on #388/#390/#314, but the stall path is separate.
  • Evidence: original stall in 20261108-trace2 drop 12; later high-load campaign retained (latest twelve drops: nine returned, two lost-window, one #390). Traces stay. Issue remains open and unverified.
  • Blocker: no more loaded #377 verification runs under prior authority. Hardware/GUI must serialize the live-test lock with other lanes. Do not reopen loaded 40-drop campaigns unless Sam issues new authority.
  • Independent work now: root-cause analysis from retained traces; engine-test reproduction without a loaded live campaign; non-loaded regression checks only. Active owner: T3 thread "Finish Window Slides After Drops" (695aec39-f7b8-4f80-96c6-e6064b459c0a).
  • Next step: continue analysis/engine-test on an isolated worktree off current trunk; no fresh loaded live runs; leave both acceptance items unticked until verified.
  • Closed 11 Nov 2026: both criteria verified on main 3e5523ac (engine f238891, paneru#156 via #413). The stall was the engine despawning a live window on an AX timeout, the same cause as the lost windows, so it is not a separate path. See the closing comment.
  • Links: https://github.com/samanyudas/miri/issues/377 · https://app.t3.codes/threads/355505e5-07e5-45f5-9353-4bf5955b575f/695aec39-f7b8-4f80-96c6-e6064b459c0a

Status update (coordinator, 10 Nov 2026 4:20 AM Adelaide)

Dependency change: traces show the #377 "stall" and the lost-window drops are the same bug, not separate paths as the earlier dependency note said. In retained trace 20261108-trace2, T3 Code is entity 365v0 through drop 12 and returns as a new entity 177v1 on the next drag. When a drag crosses displays, macOS reports the window leaving a Space; the engine keeps it only if a fresh accessibility check succeeds, and a busy app missing the 0.25 s timeout was counted as a closed window. Run 20261109-052022 logged that timeout right before the despawn on two drops.

Candidate fix: paneru#156 (head 50f56bc): a timed-out check no longer counts as a closed window; only an app-reported error does. New engine test fails on the old rule and passes with the fix; 1205 engine tests pass; Clippy and rustfmt clean; Sol (gpt-6.1-sol high) approved round 1 with no findings; make e2e selftests passed under the live-test lock; all 4 PR checks green. Side effect worth noting: this applies to every window-existence check, so a really-closed window in a busy app now waits for the destroyed notification or later sweep.

Blockers: not merged. The live criterion has not run on the fixed engine (loaded runs are paused pending Sam's authorisation), so both acceptance items stay unticked. The Miri pin PR can only open after paneru#156 merges (check-compat requires the pin on miri/v0.5.2).

Next step: Sam decides on merging paneru#156 and on one loaded verification run; then the owner thread opens the Miri pin PR.

Note

🤖 Claude Opus 5.5 responding on behalf of samanyudas

Fixed on main by paneru#156, pinned by #413. Both acceptance criteria were checked on the landed code, so I am closing this issue.

Cause. The window was not stalled. The engine had removed it. When macOS reports that a window left a Space, the engine kept the window only if a fresh read of its AX role succeeded. A busy app does not answer that read within the 0.25 s AX timeout (kAXErrorCannotComplete), so the engine treated the live window as closed and despawned it partway through the slide. In verify-miri run 20261109-052022, the traces of two drops that lost T3 Code show this sequence: a Space notification, then is_alive 400351 role err=CannotComplete 0.25 s later, then Removing despawned entity. This also corrects the note above that "the stall path is separate". The stall and the lost windows seen in the #314/#388 sessions have the same cause. paneru#156 counts a window as closed only when the app answers the read with an error.

Criterion 1, engine test. drag::a_busy_apps_window_sliding_back_after_a_drop_stays_in_its_column:

  • Before the fix, on engine 24fef41, it fails with layout [[0], [2]]: window 1 leaves the layout.
  • After the fix, it passes with [[0], [1], [2]].
  • CI ran make engine-test on #413's head ddf8e8f0 (run 42776593850), with the same engine pin as main (f238891): 1206 passed, including this test.

Criterion 2, live. Miri Developer was built from main 3e5523ac with engine f238891, on macOS 27.0.1 on a MacBook Air M3, with the 27-inch display above the built-in display. A tool dragged T3 Code (Nightly), in its 1676 by 1394 column, by the title bar below the 27-inch display's bottom edge and released it. Before each drop, the script waited until the 1-minute load average was above 10. yes processes held it there. A recorder read the window frames every 5 ms. Every run held the live-test lock, and its verify-miri end restored the previous build and passed the layout check.

verify-miri runDropsLoad before each dropEnded at the slot (x=378 y=38 w=1676 h=1394)
20261110-1815241-1211.4 to 14.912
20261110-18232313-1714.4 to 15.85
20261111-00284218-4010.4 to 15.923
Total4010.4 to 15.940

No drop stopped partway, lost the window, or ended on another display. In run 20261110-182323, Sam asked me to pause, which interrupted one more drop. That drop is not counted, and drop 18 was run again in the next session.

Limits.

  • The drags were posted by a tool, not done by hand.
  • The load reached 10 to 16 from yes processes, not from the original Rust builds and VM (15 to 50).
  • This engine has no trace lines for the role read, so the run does not show which drops took the kAXErrorCannotComplete path.
  • A different defect showed up on the pre-merge build, in 2 of 39 drops: the column came back 2 to 4 pt wider. It is tracked as #414. It did not occur in these 40 drops.