Windows refuses file symlinks with ERROR_PRIVILEGE_NOT_HELD (1314) unless
the test process is elevated or Developer Mode is enabled, which broke
non-admin local runs. Share a helper that reports the missing privilege so
the two symlink tests skip only the symlink-dependent assertions.
* fix(windows): prefer pwsh for the default pane shell
Windows panes launched Windows PowerShell 5.1 (powershell.exe) whenever [terminal] default_shell was unset, ignoring PowerShell 7 even when pwsh.exe resolved on PATH. Resolve the unset default against PATH and prefer pwsh.exe, falling back to the inbox powershell.exe. An explicit default_shell still wins.
* fix(windows): validate pwsh before using it as the default shell
portable-pty resolves the configured shell with Path::exists and passes that path to CreateProcessW, which does not fall through to later PATH entries. Selecting an invalid pwsh.exe would therefore break new panes instead of falling back. Only prefer a pwsh.exe that starts with the PE MZ magic, and return its path so the validated binary is the one launched.
* fix(windows): validate the full PE header before preferring pwsh
Checking only the DOS MZ signature still accepted truncated images, DLLs, and foreign-architecture binaries. Because portable-pty passes the resolved path straight to CreateProcessW without trying later PATH entries, validate the PE signature, the COFF header (machine, executable-image bit, not a DLL, section count), and the optional header before preferring pwsh.exe.
* fix(windows): accept native ARM64 pwsh and validate the section table
Herdr ships an x64 Windows build that also runs on Windows ARM64 under x64 emulation, so cfg!(target_arch) cannot tell whether a native ARM64 pwsh.exe is launchable. Accept every executable machine type Windows supports instead of rejecting ARM64 on the x86_64 build. Also bound-check the section table (40 bytes per declared section) after the optional header so a truncated table falls back instead of being selected.
* fix(windows): detect the native host machine for pwsh compatibility
Machine compatibility was derived from cfg!(target_arch), which reports the emulated x64 process on Windows ARM64, and then briefly accepted ARM64 on every host. Read the native machine with IsWow64Process2 in the platform layer instead: ARM64 Windows accepts ARM64/x64/x86 images, x64 accepts x64/x86, and x86 accepts only x86.
backup_preserves_legacy relied on a freshly created temp file already having a legacy DACL (D:(...)). Directories that grant inheritable ACEs produce D:AI(...) instead, so the fixture failed before exercising the backup path. Install the legacy DACL explicitly, preserving the existing ACEs while clearing the auto-inherit and protected control bits.
refs #4153
Root cause:
A no-focus pane move can remove the source tab without changing the
server's numeric workspace and tab coordinates. PaneMove is not classified
as a topology change, so a client viewing that tab retains its deleted ID.
Its target lookup falls back to the server default and can follow a later,
unrelated focus change instead of staying in the source workspace.
Fix:
Include PaneMove in the existing client-location reconciliation path.
Extend the existing focus-guard test with a client on the removed tab and
verify that a later server-default change cannot move its view. Preserve
the other client's view and existing error, no-op and zoom assertions.
Validation:
The minimized regression fails five times on PR #4159's unchanged head and
passes five times with the one-line fix. All 19 pane-move tests, native
lint, maintenance, architecture and integration-asset checks pass.
Full native just ci on the publication branch, including the unchanged
prerequisites from #4159 and #4168, passes 3585 Rust tests with six default
skips plus lint, 106 maintenance, six architecture and 39 integration-asset
tests. All seven docs contract tests pass. Local Windows cross-lint was
not run because its SDK is unavailable.
No geometry policy, protocol or periodic rendering behavior is changed.
* fix: detect codex activity with static titles and queued inputs
refs #4092
Codex 0.154.0 can omit the activity bullet when tui.animations is false
and replace the literal Working label with a task-specific label. Its
terminal title can also remain static during an active turn, so the OSC
spinner rule is not sufficient to identify work.
The screen fallback required a bullet followed by Working in the last
three non-empty lines. It therefore missed both the animation-disabled
status line and dynamic labels. Queued follow-ups add a heading, input
text, and an edit hint below the activity line, moving that line outside
the three-line region. The lower-priority non-empty OSC title rule then
reported idle while Codex still showed an active timer and interrupt
control.
Recognize the elapsed-time and esc-to-interrupt controls in the detection
snapshot before the current composer. Allow an optional bullet, a dynamic
activity label, and queued follow-ups below that line. Require that no
later response, completion, or interruption marker supersedes the live
activity. This excludes composer text and stale output without letting an
earlier interruption suppress a subsequent active turn.
Keep OSC state precedence and transcript-viewer skip behavior unchanged.
Update the bundled and distributed manifests together to 2026.09.14.1.
Add regression coverage for optional bullets, dynamic labels, queued
inputs, resumed work after interruption, and stale or quoted controls.
Avoid discarding Regex search caches on every detection poll. The loader
previously cloned the entire compiled rule vector, and each Regex clone
created a fresh search cache pool despite sharing compiled read-only
state. Share the immutable rules through Arc instead. Reload publishes a
new array while in-flight readers retain a valid old generation, without
holding the global cache lock during matching. Add a cheap contains gate
for the required interrupt text before running the multiline expression.
Add a deterministic regression for compiled-rule sharing, reload
replacement, retained old readers, and concurrent detection. Measure the
baseline and optimized detector kernels in three alternating rounds on
CPU 24 with NUMA node 0 and CPU 25 with NUMA node 1. Real queued detection
falls from about 116 to 22 microseconds, and two evaluations per pane over
15 panes fall from about 3.45 to 0.65 milliseconds on both sockets, about
81 percent less time. No benchmark match results change. The cache
improvement requires the updated binary, not just a manifest reload.
Validate on the official Herdr 0.9.0 release with Codex 0.154.0 by toggling
the old and new manifests during the same active queued task. Confirm
working for active turns and idle after completion or interruption.
Pass 86 captured-screen and semantic checks through the optimized source
binary, 69 manifest/cache tests, CPU-pinned render scaling, and the full
native CI suite: 3535 Rust tests passed, with six default skips. Run CI
serially with inherited Herdr session context removed; no tests were
excluded or expectations relaxed to make CI pass.
* fix: detect codex pending follow-ups as working
refs #4092
* fix: handle wrapped codex follow-up hints
refs #4092
---------
Co-authored-by: Ogulcan Celik <ogulcancelik@gmail.com>
refs #3937
Root cause:
Activating another endpoint applies a snapshot with a different
endpoint-qualified graphics scope. This triggers reset_endpoint_projection,
which clears agent_scroll together with endpoint-local state even though
the agent list aggregates all machines. The selected agent receives focus,
but the list jumps back to the top.
Fix:
Save the client-owned agent scroll offset and restore it only when
activation changes endpoints. Keep same-endpoint reboot resets,
workspace/tab scroll resets, input cleanup and existing list bounds
clamping unchanged. This adds constant-time work on endpoint activation,
with no protocol, persisted-state or pane-scaled rendering changes.
Validation:
The new regression fails on the original implementation with scroll 0
instead of 6. Tests cover repeated bidirectional agent clicks, stable
visible rows, shrinking lists and same-machine reboots. All 242 client
shell tests pass.
Real SSH A/B testing against the same two disposable 0.9.0 servers
reproduces the reset with both the stable and current-master clients.
The patched client preserves the visible rows through six cross-machine
switches and two same-machine control clicks.
Native just ci passes: 3535 Rust tests, six default skips, plus lint,
maintenance, architecture and integration-asset checks. All seven docs
contract tests pass. Windows cross-lint was not run because its SDK is
not installed; native macOS and Windows UI behavior was not tested.