What: DesktopBuildLock.acquire takes the checkout lock (update_lock, via
UpdateLock.acquire_checkout without the marker) before its own file lock and releases it after.
One lock order everywhere: checkout, then desktop build. The update's own build already holds the
checkout lock and only joins it again (depth), so it never waits on itself; a process already
holding a checkout lock under another path spelling builds inside that custody. wait=False (direct
`hermes desktop`) is refused while an update holds the checkout; wait=True queues behind it; a lock
that cannot be created raises OSError, which cmd_gui already reports as "Refusing to run npm
without serialization". cmd_gui's contended message now names a running update too (same line,
main_desktop.py does not grow).
Why: a direct cmd_gui build held only DesktopBuildLock, so another process could take UpdateLock
and write the same checkout (node_modules, release output) while npm wrote (review C5). Holding
the checkout lock also puts the direct build's npm tree in update custody (POSIX: the lock fd
under the reap launcher; Windows: the kill-on-close job and C3's tree reap).
Tests (test_desktop_build_lock): a held DesktopBuildLock refuses a second process's UpdateLock,
and a running update refuses a direct build without leaving the desktop lock held - both RED on
fd5c37f17bf, GREEN here; control: the update path's own DesktopBuildLock joins its checkout lock
and its release leaves the update's hold intact. test_gui_command's npm double now finds the npm
argv behind the custody launcher prefix (the direct build runs in custody now); no assertion changed.
main_desktop.py grew past its code-health cap (2088 > 2069 on main). Move the
existing, self-contained `hermes gui --setup-tcc-identity` cluster
(_desktop_macos_setup_tcc_identity, _macos_codesigning_identity_valid,
_macos_create_signing_identity) verbatim into the topical sibling
main_desktop_tcc.py. cmd_gui late-imports it; the sibling late-imports the
facade's _desktop_packaged_executable/_desktop_macos_relaunchable_fixup, so the
existing monkeypatch seams on main_desktop still apply. main_desktop.py is now
1933 lines (>= 15 lines headroom under main). Tests and the resolution
allowlist row follow the moved symbol. Behaviour unchanged.
The downgrade guard added a codesign probe inside the cmd_gui launch path,
which leaked /usr/bin/codesign calls into
test_packaged_launch_opens_the_refreshed_installed_app's captured
subprocess.run. The test pins the LAUNCH contract only — with no codesign
binary the signature checks are off and the swap proceeds, keeping it
focused (signature policy is pinned separately in
test_desktop_install_after_update.py).
The rebase of the signing-downgrade guard dropped the unconditional
`xattr -cr` from _desktop_macos_relaunchable_fixup (the commit that claimed
to reorder it after the identity decision deleted it instead), so locally
built bundles kept their quarantine attributes and still tripped the
'Hermes is damaged' Gatekeeper path the strip exists to prevent. Restore it
unconditionally ahead of the signing attempt: in the stage-and-swap flow it
targets the STAGED bundle (discarded when the fixup refuses), and in-place it
is harmless hygiene on a bundle whose signature is never replaced.
test_relaunchable_fixup_configured_identity_failure_never_falls_back_to_adhoc
now models the reconciled policy from #123748/#121857: it pins a
publisher-signed (Team ID) replace-target — refusal, no ad-hoc, remedy named —
while the locally-signed target's identifier-pinned ad-hoc retry stays covered
by test_relaunchable_fixup_failed_identity_uses_pinned_adhoc_before_legacy.
Review follow-up on the macOS signing-downgrade guard:
- _promote_staged_desktop_app no longer discards the fixup's False: a
refused signing (configured identity failed / codesign missing) now
discards staging and raises the same previous-app-kept RuntimeError
the integrity check uses, so the live bundle is never swapped for one
whose signature was never established. Regression drives the real
promotion path end-to-end (the earlier tests only called the fixup
directly).
- Every downgrade-refusal message now names the next action (publisher
identity for the locally-signed branch, the desktop signing setting
for team mismatch, bundle identifier alignment, codesign -vvv for
strict-verification failures), matching the running-app message.
- The fixup reads the identity decision BEFORE the quarantine-xattr
strip: a failed configured attempt no longer strips attributes off a
bundle it then declines to modify.
Red on upstream/main, green with the fix:
- _install_rebuilt_macos_bundles refuses to replace a Team-ID-signed
install with a locally signed build, a different Team ID, a different
bundle identifier, or an unverifiable rebuild; matching publisher
identities and ad-hoc installs keep swapping.
- _desktop_macos_relaunchable_fixup keeps the existing signature when the
configured desktop.macos_signing_identity fails instead of degrading
to legacy ad-hoc.
A locked login keychain makes the configured identity unusable during an
SSH update. Retry the identifier-pinned signer before the cdhash-only
legacy deep sign so TCC grants and entitlements survive.
Nested frameworks were signed with no entitlements, so under the hardened
runtime Electron Framework lost com.apple.security.cs.allow-jit and V8
crashed on launch. Apply the inherit plist to every nested bundle, matching
what electron-builder's afterSign step does in a real build.
A preview guest that HTML5-fullscreens itself owns all input: the host
renderer can't see into the out-of-process webview, the macOS-only Close
menu and HUD chords aren't reachable, and Wayland has no xdotool/wmctrl to
break out from a terminal — so a page that swallows Esc locks the display
(#97213).
Three layers, all routed before the guest sees the key or fully outside it:
- before-input-event on every webview guest: Esc exits the host window's
fullscreen (gated on the fullscreen state so normal-mode panes keep Esc),
Ctrl/Cmd+Shift+W closes the pane via the existing close-preview channel
- hermes://close-preview deep link handled in the main process: restores
and focuses the window, exits fullscreen, closes the pane
- `hermes desktop --close-preview` CLI flag forwarded to the packaged exe,
riding the single-instance argv so a second invocation unlocks the running
app
The permission handlers keep auto-allowing 'fullscreen' — the exit paths
are the fix, and scoping the grant would just break video fullscreen.
Refs #97213
Mark the win32 rows with platforms("windows") instead of patching
sys.platform: the repo runs host-specific behaviour on that host, and the
Windows Python-tests lane already imports this file.
Review follow-ups on the Windows skip:
- Most people who hit the boot loop launched Desktop from the Start menu and
never open a terminal. The in-app update also rebuilds the app: the
Windows shim waits for Desktop to exit, and the already-up-to-date path
still completes with desktop=True. The notice and updating.md now name
Update now in Settings -> About next to `hermes desktop`.
- cmd_gui took the skip's None as "no launchable app was found". A build
that fails raises instead of returning None, so None there only means the
skip, and `hermes desktop` now reopens the app that was kept.
Tests fold to the two invariants, each red when its half of the fix is
reverted: the stop spares its ancestor Desktop on every platform, and a
Windows packaged build under its own Desktop is skipped (now driven through
the real _desktop_ancestor_in with a fake process tree, instead of a stub).
An update interrupted after its dependency sync leaves source-completion-
pending, and the next Desktop launch finishes it from the Desktop's own
backend (venv_sync._finish_source_update). On Windows the tail's desktop
build reached _stop_desktop_processes_locking_build, which spared ancestor
Desktops only on POSIX, so it terminated the Desktop it was running under.
The pipes every child logs through went with it, the whole tree died before
the tail could clear its markers, and every launch repeated it (#123499).
Stopping an ancestor can never free the exe lock for the process doing the
rename: that process dies first. The ancestor spare now applies on every
platform, and build_prepared_desktop skips a Windows packaged build running
under its own Desktop with a notice, since its promotion could not succeed
while that Desktop holds the lock. The tail finishes and clears its markers;
the app's content stamp stays stale, so `hermes desktop` run outside the
app rebuilds it.
Closes#123499.
YAML parses a bare `0` as int, which hit neither the bool nor the str
branch of _desktop_launch_options, so the unquoted off-switch silently
kept the tree on. Accept numeric scalars explicitly (bool checked first —
it is an int subclass), and add `disabled`/`enabled` to all three word
lists (launcher _A11Y_OFF_WORDS, readDesktopLaunchConfig, RENDERER_
ACCESSIBILITY_OFF_WORDS) so both launch paths accept the same
vocabulary.
Chromium only builds the renderer's accessibility tree when it detects
an assistive technology — and dictation tools that insert text through
the OS accessibility APIs (Wispr Flow etc.) do not register the way a
screen reader does, so the tree never appeared: the window exposed only
its chrome and the contenteditable composer was unreachable. No
setAccessibilitySupportEnabled call existed anywhere in the electron
tree, on any platform.
- apps/desktop/electron/renderer-accessibility.ts (new): the
platform/env decision in a dependency-free module (app object
injected) — ON by default on darwin/win32 where the typed API exists,
opt-out via HERMES_DESKTOP_RENDERER_ACCESSIBILITY=0. Linux is untouched
(ATK flow needs no manual enable; the API is not typed there).
- main.ts calls it right after app ready (the API's requirement).
- hermes desktop launcher: `desktop.renderer_accessibility: false` in
config.yaml bridges to the env var — only the opt-out is bridged, so
the default never depends on the launcher path. Same shape as
desktop.disable_gpu. config_defaults documents the key.
Family note: the same seam on Windows is #92607 (Wispr Flow there);
this one enablement should resolve both platforms, but the Windows leg
is unverified on this machine, so #92607 stays open. The dictation
keybind half of #118271 already landed separately in 3cc24b0130
(composer.dictate action; rebindable-key ask tracked by #112118/#71658,
left alone).
Tests: 4 electron-side unit tests (default-on per platform, Linux skip,
opt-out words + explicit env precedence, injected app switch flips
exactly when enabled); Python side: 12-case normalization parametrize +
env-bridging precedence test (ON sets nothing, opt-out bridges, explicit
env wins) in test_gui_command.py; _desktop_launch_options signature
consumers updated. All green; the remaining lucide-react TS2307 in
onboarding setup.tsx is pre-existing on base (missing dep of that
workspace file, untouched here).
Fixes#118271
On macOS `hermes desktop` launched the release/ bundle even when an
installed Hermes.app existed, so the app ran from two paths (two Dock
icons, TCC grants keyed to a different bundle) while the one Finder opens
stayed stale. Refresh the installed copies first and launch the one that
now matches the checkout build; release/ stays the fallback.
The CI Linux job aborted mid-file with a session-wide KeyboardInterrupt: the
test patches hermes_cli.main.subprocess.run for the WHOLE process, and
_desktop_launch_env -> _detect_linux_password_store probes
org.freedesktop.secrets via dbus-send BEFORE the launch (a bare runner has no
KDE/GNOME session vars). contextlib.suppress(Exception) cannot swallow
KeyboardInterrupt (BaseException), so it escaped cmd_gui and pytest treated
it as a user Ctrl-C, killing the rest of the file — '31 passed, 13 skipped'
then non-zero exit.
Neutralize _detect_linux_password_store the way the other foreground tests
do; the macOS job never hit this because the probe is behind
sys.platform == 'linux'.
The Ctrl-C test's global subprocess.run patch also intercepts the Linux
sandbox fixup's `unshare --user --map-root-user` user-namespace probe,
which cmd_gui runs from _packaged_desktop_launch_command BEFORE the
attached launch — outside the KeyboardInterrupt handler under test. The
interrupt escaped and aborted the whole pytest file (CI Linux:
'test_gui_command.py:1138: KeyboardInterrupt' on the merge ref; reproduced
in a Linux container with --full-trace showing the probe as the escape
point). Stub _desktop_linux_userns_sandbox_available to True, matching the
file's convention of neutralizing platform plumbing the test is not about.
Both pre-launch subprocess callers (desktop-entry refresh, unshare probe)
are now neutralized; the only mocked subprocess.run call left is the
attached launch itself.
Patching hermes_cli.main.subprocess.run swaps the shared stdlib module's
attribute, so every subprocess.run in the process raises. On Linux the
best-effort desktop-entry install runs refresh_desktop_databases via
subprocess.run BEFORE the attached launch — outside the KeyboardInterrupt
handler under test — so the mock's interrupt aborted the whole pytest
process (CI: 'tests/hermes_cli/test_gui_command.py:1070:
KeyboardInterrupt'). Neutralize _register_linux_desktop_entry the way the
other foreground tests in this file do.
A Homebrew (or other pip/package-manager) install lives in a
site-packages tree that does not ship apps/desktop, so `hermes desktop`
failed with a generic missing-source error implying a broken checkout.
Detect the install kind (Cellar/site-packages), prefer launching a
separately installed /Applications/Hermes.app, and otherwise print a
brew-specific instruction instead.
Fixes#61056
A v2026.7.1 Linux Desktop runs `hermes update` as a piped child. The swap
already spares that Desktop's main process, because it is our ancestor. But its
zygote, renderer, GPU and network-service helpers run the same release exe and
are not our ancestors, so the swap still stopped them. E2E agent.log shows six
PIDs stopped before the staged app promotion. At that moment the renderer's
backend WebSocket dropped with 1006, and desktop.log and the screen recording
froze. The update receipt then succeeded, but the main process had no renderer.
It never relaunched and never quit, so launch-from-spec hung in the post-update
phase until the 20 minute self-timeout (exit 124).
Spare the whole process tree of a Desktop ancestor that runs from the release
tree. Only a Desktop ancestor's descendants are spared: every process descends
from init. Other Desktops running from the tree are still stopped (#109643).
Windows behavior is unchanged.
A v2026.7.1 Linux Desktop runs `hermes update` as a child with piped
stdout/stderr and relaunches itself afterwards. The build swap stopped every
process running from the release tree, including that ancestor, so the
update's next write failed with EPIPE and nobody relaunched the app.
Spare our own ancestors on POSIX, where the rename works with the app alive.
Windows keeps stopping them because the exe lock would fail the rename.
Change-detectors, tautologies, source-reading tests, redundant duplicates,
mock-echo tests and dead/unrunnable tests. Per-test rationale in the lane
ledger (category + reason for every removal).
Branch semantics kept where main and PM disagree: update_cmd_deps.py,
constraints-termux.txt, the Electron update-api-check module and the
post-swap hand-off test stay deleted; the pending-fleet-restart catch-up
and the local_runtime tag/download ladder stay retired (PM owns engines).
Ported from main onto the branch's shape: profile_scoped_chore for the
auto-archive and plugin-update housekeeping chores, the local-runtime
cross-process boot lock and residency cap, the checkpoint tmp_pack sweep,
the cua daemon-liveness status probe, the remote-served Desktop update
flag (posix.sh / windows.ps1), sign-in for env-pinned remote gateways
(urlDisabled on RemoteSetupFields), the uvloop extra split (uvicorn
without [standard]), and the umask-scoping spawn test.
uv.lock regenerated with pm.build_env --lock-only; new utf-8 reads from
main switched to utf-8-sig (check-windows-footguns).
The stage-and-swap promotion stops running desktop processes before
renaming the live app aside, but _stop_desktop_processes_locking_build
returns early off Windows: POSIX can rename a running app's files away,
so the pack needs no lock release. A renderer left alive through the
swap keeps fetching its OLD hashed chunks by path from the new tree and
dies on the next lazy import with 'Failed to fetch dynamically imported
module' — the app stays a broken UI until relaunch, even though the
swapped-in bundle itself is complete (#85868; also reported as #96375
and #109643).
Pass also_posix=True from the swap point only: the pack-time calls stay
Windows-only because the staging pack never touches the live tree.
Fixes#85868
The pack-recovery mirror rung fired on "the pack failed and the staging
output holds no packaged executable". Every failure before electron-builder
is also true of that condition, and no mirror can repair a compile, bundler
or native-link failure — the rung just re-ran the whole pack while printing
that the Electron download from GitHub looked blocked.
The retry now also requires _electron_dist_ok to be false, matching the
gate the re-download rung already used. Late failures are unchanged: a
packaged executable in place still means exactly one attempt (#40187).
Reconcile plugin declarations and validation through PM's atomic generation publication; preserve external runtimes, target markers, and conflict refusal. Keep one source-update completion owner and port upstream lifecycle changes to the PM desktop/runtime paths.
- cmd_gui with DESKTOP_STARTUP_ID: the entry is installed only after the fake Electron
writes the reveal byte to HERMES_DESKTOP_READY_FD (spawn precedes install).
- cmd_gui without DESKTOP_STARTUP_ID (control): install still precedes the spawn.
- DeferredDesktopEntryInstall.finish(): an app that exits without revealing heals
exactly once, after the exit.
- linux-launcher-ready: one byte, fd closed, variable consumed; garbage/absent = no-op.
Docs: user-guide/desktop.md explains when the entry write happens for grid launches.
Keep desktop rollback in staged publication instead of restoring backups
inside a candidate directory that will be discarded.
Use the npm tag endpoint, let the downloader own archive verification,
and ensure CI toolchain roots without repeating dependency verification.
Remove an unused resolver input and replace overlapping tests with
fault-proven lifecycle coverage.
Verified with the scoped Python gate and before-pack tests. Native
Windows/macOS update execution and the full suite were not run.
Competing installers and checkout-local venv assumptions bypassed PM
selection, install consent, and generation lifetimes. Route consumers
through PM and installation-bound launchers. Refresh source launchers
before obsolete Python entries can be collected.
Remove Node, browser, and CUA acquisition engines, obsolete venv-holder
handling, detached sync, and unused PM APIs. Keep historical updater
exports inert and preserve external tool ownership and native integration.
Share product freshness and prepared inputs across builders. Align plugin
admission, Docker provisioning, setup instructions, and behavioral tests.
Verified targeted Python and JavaScript tests, desktop and web typechecks,
scoped lint, real product builds, and the Docker frontend smoke test.
The missed post-setup test cleanup is included and verified.
Native Windows/macOS execution, full Rust compilation, and the complete
repository suite remain unverified. Historical compatibility requirements
were preserved and extended, not fully rescanned.
Service PATH uses the selected dependency generation and the target home managed Node records. Preserve lexical Python paths in desktop launch tests and create the staging artifact in the GUI build fixture. Remove unsupported classifier action inputs.
Canonical Windows selection passed 48 tests and skipped 57. Linux service assertions require the native CI lane; skips are not execution proof.
Prepare dependency generations before selecting them. Keep shipped tool
bytes separate from writable additions, and store facts beside their entries.
Validate proposed plugin sets before config publication. Restore the previous
config if the facts write fails.
Consolidate duplicate updater, backup, setup, and voice helpers. Repair
launcher selection, dependency consumers, download ownership, update feeds,
and native Windows process and file handling.
Verification: 206 changed/prior-failing Python files reported 4630 passed,
one failed, and 330 skipped. Fix the remaining Hindsight fixture boundary.
The final targeted rerun reported 234 passed and two skipped. The store
review regression batch reported 83 passed and one skipped. Desktop
TypeScript checks, 56 selected Electron tests, 24 release tests, and the
removed-import/compatibility guards passed.
This is an integration checkpoint, not full audit acceptance. The complete
Python suite has not run on this fixed tree. Crash-atomic plugin publication,
generation cleanup, receipt correlation, and packaged lifecycle acceptance
remain open in docs/pm-audit-status.md.
The earlier adopt didn't replace this test's subprocess side_effect
(multi-line form evaded the string replace). Now mocks with
_pack_into_staging(root) so cmd_gui's pack produces the staged tree and
reaches the launch run — fixing the macos-lane IndexError.
Our merged cmd_gui packs into a staging dir (stage-and-swap, #86443)
then renames over release/; the e97-era tests mocked pack w/ plain
CompletedProcess and never laid the staged exe down, so post-merge the
pack-then-verify path exited 1 (macos password-store test: IndexError,
no 2nd run). Adopted upstream's _staging_dir_from/_packaged_exe_rel/
_pack_into_staging helpers + the staging-aware test bodies for the
pack/launch + password-store family. Locally: 32 pass, 2 pre-existing
Windows-host failures (linux-marked tests exercising /usr/bin/npm
paths).
Upstream's linux_desktop_entry.py (806 lines) supersedes the pm-era
227-line version: hicolor icon install with PNG resize, launch-context
independent Exec, running-interpreter venv-semantic resolution. Take it
wholesale; _repo_pythonpath_entry had no external callers.
test_linux_desktop_entry: use upstream's icon-fallback test + keep the
quote-aware Exec assertion (Windows paths trigger _quote_exec_arg).
test_gui_command: restore pre-upstream-merge version — our main.py is
the pm-era one, so upstream's newer tests expect behavior we don't have;
2 remaining failures are pre-existing Windows-host npm-seam issues.
`hermes update` → `hermes desktop --build-only` → `npm run pack` packed
electron-builder's output IN PLACE: before-pack.mjs wipes
`release/<platform>-unpacked` (or the mac `Hermes.app`) before the Electron
unpack/asar/rename, so any failure after that point — corrupt cached zip,
blocked download, missing dep, disk full — left the user with NO app and the
update reporting "partially complete" over an empty release/ (#86443).
Fix the class, not the predicate: cmd_gui now passes
`-c.directories.output=apps/desktop/.staging-<pid>-<ts>` to the pack, runs
the existing verification (packaged-exe probe, macOS re-sign, Windows PE
integrity gate) against the STAGED tree, and only then promotes it:
`release/<unpacked>` → `.previous`, `<staging>/<unpacked>` → `release/<unpacked>`,
drop `.previous`. A rename failure between the two steps restores `.previous`.
On any failure the staging dir is removed and the live app is untouched.
- `_purge_electron_build_cache` / `_ensure_desktop_exe_launchable` /
`_desktop_macos_relaunchable_fixup` take the output dir so the corrupt-zip
retry purge and the integrity self-heal only ever clear the staging tree,
never `release/*-unpacked`.
- `.gitignore` the staging dir so a killed build cannot dirty the checkout.
- Docs: updating.md describes the stage-and-swap Desktop rebuild step.
Live repro (real `_rebuild_desktop_after_update` → real `hermes desktop
--build-only` subprocess, fake npm whose pack wipes appOutDir then fails):
before — `release/linux-unpacked/hermes` gone after the failed rebuild;
after — marker intact, no `.staging-*` left, rebuild returns False; a
passing pack swaps the new app into `release/`.
Closes#86443
Co-authored-by: AIalliAI <285906080+AIalliAI@users.noreply.github.com>
Co-authored-by: deathxdefeat <deathxdefeat@users.noreply.github.com>
When chrome-sandbox is present but not root-owned 4755, Chromium can still
abort via setuid_sandbox_host even though the namespace sandbox works.
After the userns probe skips sudo, append --disable-setuid-sandbox so
.desktop/no-TTY launches keep the namespace sandbox without a privilege
prompt. Does not add --no-sandbox.
Fixes#51327
Wire the desktop app onto the pm store for real distribution:
- MSIX bundle: electron-builder config, appx assets, manifest, copilot
key + deep-link routing, App Installer + Windows Store variant
(sign only the msix; inner binaries covered by the package block map)
- Rust CLI shim (apps/desktop/shim) — bundled builds run from the store
python + shim, never the venv; payload symlinks relativized so the
relocatable venv survives relocation
- Cloudflare R2 release pipeline: publish binaries + update feeds,
nightly channels/tags, stamp-first version resolution
- Update system: gate, uninstall steward, boot bootstrap, release
channels, update receipts
- install.ps1 reduced to a 361-line stage-protocol bootstrapper (heavy
deps are pm's job); darwin updater + update-channel mirror ripped
- doctor: main's re-landed TCC anchor kept, termux branches removed
Rebuilt from ethie/pm onto the pm-store stack. 22 hot files hand-merged;
uv.lock + package-lock.json keep main's newer dep tree; test_engines
reads the pm/lock.json pin; lazy_deps.py deleted (all 222 importers
migrated to pm in the foundation commit).
No shims, one marker system. The full mechanical sweep:
- 165 test files converted: every @pytest.mark.<os>_only decorator and
pytestmark assignment is now platforms("<os>"). Docstring/comment
prose mentioning the trio rewritten to the platforms() vocabulary.
- conftest: _OS_MARKS, the legacy skip loop, the double-mark reject for
the trio, and the selector-tagging shim are deleted. The single
platforms() gate does all host gating; _reject_contradictory_platform_
marks replaces the old reject (one platforms() marker per test — a
module-level pytestmark stacked on a per-test marker is the historic
skipped-everywhere-green-everywhere failure and stays a hard error).
- pyproject: only the platforms marker is registered.
- list_os_marked_tests.py: rewritten for the single vocabulary — takes
a platform name (linux/macos/windows), matches quoted platforms(...)
specs including negated and any-of forms, exits nonzero on empty
selection. Its test file rewritten to match (bare identifiers must
not match; the spec must appear inside the string literal).
- tests.yml macOS lane: marker: macos feeds the lister; the pytest
selection is -m "platforms and not integration" — the marker name is
the selector, the conftest's per-test host skips are the gate.
- test_os_marker_gating.py rewritten for the new reject (the old file
tested deleted machinery).
- AGENTS.md / CONTRIBUTING.md updated to the single vocabulary.
Verified: full-tree compile; marker unit tests (36); lister tests;
macOS-lane simulation (21 files, 180 collected / 429 deselected on a
windows host); broad xdist slices through scripts/run_tests.sh
(367 + 1674 passed, zero refactor-attributable failures — the 6 red
tests in the second slice fail identically with the changes stashed).
Gate the POSIX-only and symlink-only tests with the linux_only and
require_symlinks markers. Fix the real cross-platform bugs:
- file_operations: use the translate_path flag, send snippets as base64, and
use sys.executable (the MS Store python3 stub and the list2cmdline
backslash collapse both broke snippets)
- approval: treat backslash as a Windows path separator, not an escape
- registry, browser_registry, secret_sources, plugins: normalize scope-key case
- checkpoint_manager, plugins_cmd: clear read-only bits before delete
- deadline: make MAX_SAFE_TIMEOUT_S fit the Windows limit
- image_routing, acp, cua_backend, daytona: fix Windows and POSIX paths
- hermes_state: match backslash in the retag LIKE clause
- kanban, disk-cleanup: match drive paths and split command arguments
- scripts: emit host separators through as_posix
207 test files are gated or isolated.
The legacy ad-hoc fallback signed and verified successfully but still
fell through to return False, contradicting the fixup's documented
contract. The success witness codified the contradiction. Return True
on the verified success path; the caller ignores the return value, so
no behavior change beyond the contract correction.