* Run clipboard file copy and TLS openssl through process-host
* refactor(clipboard): inject runProcess, pass the Windows path as data, add an e2e
- clipboard-file-copy builds the process spec itself, so the stdio policy sits
next to the wl-copy/xclip knowledge it exists for; the IPC handler passes
process-host's runProcess straight through and keeps no wrapper.
- A helper's exit is read as data (clean exit or not) instead of being turned
into a throw and caught again.
- Windows runs a constant Set-Clipboard script and reads the path from the
environment, so no path is ever parsed as PowerShell.
- Drop the dead tls-certificate rewrite; nothing in the app imports it.
- e2e: right-click Copy on Windows, then a real shell paste of the file.
* ci(e2e): run the Windows explorer file-copy spec on the Windows runner
Route it from the clipboard sources, keep it out of the Linux lanes, and run it
in the existing Windows e2e job beside the other Windows-only specs.
* refactor(clipboard): tighten the helper seam, tests and e2e after review
- runClipboardHelper takes runProcess and a plain ProcessSpec.
- One handler test pins the process-host wiring on every host; spec details
stay in the unit tests, which now also pin the inherited environment.
- e2e: native realpath so an 8.3 runner temp path still matches, paste via
process-host, and no toast check that could never fail.
- Route the renderer caller to the e2e too.
---------
Co-authored-by: Orca Worker <orca-worker@localhost>
* fix(omp): read the cmd fresh-config path by name so the guard survives a variable hand-over
On cmd.exe, OMP's fresh-session guard referenced %ORCA_OMP_FRESH_CONFIG%,
which cmd only expands while reading the typed line. When the line reaches
cmd inside another variable (the setup gate's
`<wait> && %ORCA_SEQUENCED_STARTUP_COMMAND%`), cmd expands once, the
reference stays literal, and OMP refuses to start with "fresh OMP settings
are unavailable".
The guard now reads the variable by name with a one-pass for-variable,
`for %! in (.) do @if exist "%~$ORCA_OMP_FRESH_CONFIG:!" ...`, so typed and
handed-over lines behave the same. The older cmd prefix stays recognized
for persisted lines and older hosts. A real-cmd test runs both deliveries in
the Windows CI lane.
* test(omp): pin the cmd guard's echo suppression and type the smoke's cmd line on stdin
The real-cmd test now runs echo on, like a pane, and asserts the config
path never reaches stdout, so dropping the guard's @ fails it. The manual
Windows smoke typed the line into a batch file, where the guard's %! would
need %%!; it now feeds cmd.exe on stdin like a pane does. The guard comment
now states the real rule: only %! or %~...! text in the for body changes,
and the quoter keeps prompts from forming it.
* test: reuse automation fixtures and narrow long-poll setup
* test: load terminal RPC methods only in consuming scenarios
* ci: skip packaged Orcad lanes for unrelated unit test edits
* style: format the WSL Orcad workflow
* ci: accept validated aggregate success without queuing a runner
* test(ci): exercise aggregate rejection of an unexpected preflight skip
* ci: restore normal preflight after hosted rejection proof
* Scope Windows native compiler setup and measure ARM inbox preparation
* Add a controlled Windows native preparation comparison
* Test native preparation through an injected process runner
* Guard reviewed Windows native batch runners
* feat(relay): watch a reserve cell and flip it back on the first step 5 stop rule
A manual, inert-until-dispatched workflow samples one reserve cell every minute for up to
300 minutes against the step 5 §9 stop rules, logs one JSON line per sample, and on the
first trip (or N unreadable samples in a row) runs the flag tool's own admitMode=db write
under the flag workflow's concurrency group. dry-run logs the flag tool's plan instead.
Claude-Session: f2fe8cb5-4467-4a25-8aab-98598b70791f
* fix(relay): keep the reserve watch's flip back out of the flag group, and baseline 503s before the flip
- The flip back holds no concurrency group: GitHub keeps one pending run per group and a
newer one cancels it. The flag tool's CAS orders it instead; a generation conflict is
re-read and retried (5 tries, backoff) while the switch file still says reserve.
- The 503 background comes from the 10 minutes before a required flipped-at, or an explicit
value; neither is refused.
- A failed or cancelled write-mode watch flips back too. An unreachable cell fails with the
exact break-glass inputs.
- Missing 503 data (empty series, unpublished minute, unparseable time) is unverifiable.
- Seat-behind-row key adds the director instance and cell; six segments share one command.
Claude-Session: f2fe8cb5-4467-4a25-8aab-98598b70791f
* fix(relay): read the reserve watch's 503 publishing front from every response code
A quiet fleet has no 503 series, so the watch read it as unverifiable and flipped a healthy
cell back within minutes. The 503 read now groups request_count by response code in one
query; the newest minute any code has a point for is how far the series is published, so a
503 minute at or before it with no point is zero and minutes past it are left out.
Also: the flip-back job's steps run on always(), flipped-at ages against the watch's start
in later segments, a pending pre-flip background no longer adds to the unverifiable streak,
the flip back retries runtime-status before calling for break glass, and the concurrency
rationale lives once in the docs.
Claude-Session: f2fe8cb5-4467-4a25-8aab-98598b70791f
* docs(relay): state what a cancel during the flip-back lease wait can leave, and the 503 publish-skew assumption
Claude-Session: f2fe8cb5-4467-4a25-8aab-98598b70791f
* fix(relay): fail a segment closed on a corrupt watch state file, and keep no checkout token on disk
Claude-Session: f2fe8cb5-4467-4a25-8aab-98598b70791f
* fix(relay): alarm on a failed flip back, refuse a watch under a rollout lease, wait for reserve to apply, and control the duplicate read
- A flip back whose write or plan never finished (no ID token, a failed login, a silent cell)
raises an ::error:: annotation and a FLIP BACK FAILED summary, as the clock-skew monitor does.
- The first segment reads the Cloud SQL rollout lease through the lease action's own reader and
refuses to start while any run holds it, or when it cannot be read.
- A cell not yet reporting reserve is waited on for up to 10 minutes, judging nothing, before the
watch ends refused without a write.
- The duplicate-seat read takes the per-minute ledger summary in the same query and window; with
no fresh summary in it the sample is unverifiable, not zero duplicates.
Claude-Session: f2fe8cb5-4467-4a25-8aab-98598b70791f
* fix(relay): page on a refused reserve watch or a failed flip back
A refused watch can leave a cell in reserve and unwatched, the same as a flip back that did not
land. Both now raise an escaped ::error:: annotation through one `alarm` command, and in write
mode write orca_relay_reserve_watch_alarm with gcloud logging write. A new log-based metric and
alert policy page on any such line, per cell, through relay_alert_notification_channels, and the
deploy identity gets roles/logging.logWriter in production to write it.
Claude-Session: f2fe8cb5-4467-4a25-8aab-98598b70791f
* fix(relay): name the log-writer risk, check the alarm's project, and say a failed login cannot page
Claude-Session: f2fe8cb5-4467-4a25-8aab-98598b70791f
* docs(relay): note a force-cancel can skip the reserve-watch flip-back write
* Avoid unused module imports and trim cosmetic test coverage
* Capture hidden popup frames and attach CDP before guest creation
* Overlap relay test projects within the existing worker budget
* Consolidate native preparation and add an opt-in transform cache comparison
* Release inherited debugger sockets during final E2E teardown
* Refresh unit scheduling weights from all five qualified ARM shards
* Avoid retaining unasserted RPC call history in the delivery model
* Use the supported unconditional background wait syntax
* Overlap unconditional output upload with native preparation
* Verify relay integration stays enabled for ordinary unit callers
* perf(ci): reduce test overhead and reuse expensive build work
* test(ci): keep worker startup outside failure-case deadlines
* test: isolate push fixture advisory locks
* test(ci): preserve fixed drain retry allowance
* Align CI contracts with current triggers and guide content
* Stabilize consolidated reveal scenarios and retain behavioral CI contracts
* Fix upstream lint blockers while retaining behavior and assertions
* Classify global Jira preferences in the WSL migration inventory
* Align Jira test fixtures with startup and build safety checks
* Include package-test routing dependency in CI checkouts and fixtures
* Keep native stream classes intact in transcript dependency tests
The three popup compatibility probes ran in the parallel unit shards and
timed out at their 14s in-process deadline when other files starved the
runner. Move them into the package job's serial Electron lifecycle step,
and stop the six probes already in that step from also running in the
shards.
Claude-Session: p2-headless
* fix(relay): end the restart-safe quiet at the drain's pace window, not a window after it
A paced drain tells its last host when the window ends, so the restart-safe
wait has to see an empty runtime reaching past that point. It counted a full
window of quiet samples from the first empty one instead, and the runtime only
empties as the window ends, so every same-cap cell waited the window twice:
about 25 minutes per Asia cell at 1200 s on 2026-10-13, and 5 minutes per
300 s cell.
The window is now measured from the verifier's own start, which runs after the
cell accepted the drain and so ends no earlier than the cell's own window. A
pass still needs two consecutive restart-safe samples, the last taken at or
after that end, and any non-zero runtime counter or open migration still
restarts the count. Progress lines report paceWindowRemainingMs in place of the
old sample target.
Claude-Session: f2fe8cb5-4467-4a25-8aab-98598b70791f
* fix(relay): drop the same-cap drain timeout's double-wait allowance and stale quiet wording
The drain step's timeout added the window's excess over 300000 only because
restart-safe used to wait a second window after the cell emptied, so it is
now the 15-minute migration lease plus the window for every pace (35 minutes
at 1200000, unchanged at 300000 and below). Docs, workflow comments and the
shadow gate's settledAfterSeconds note now describe the window-end rule. The
always-quiet test starts its clock late, so a window measured from zero
fails it, and the overlapping late-control test is folded away.
Claude-Session: f2fe8cb5-4467-4a25-8aab-98598b70791f
* docs(relay): state the restart-safe window-end rule exactly
Claude-Session: f2fe8cb5-4467-4a25-8aab-98598b70791f
* docs(relay): the slowest restart-safe wait is now 35 minutes
Claude-Session: f2fe8cb5-4467-4a25-8aab-98598b70791f
Any press on a <webview> guest moves Chromium focus into it, so an agent click
took the user's terminal focus (#20595). Main now asks the hosting pane to guard
focus for each agent press (Orca's click/mouse down and the CDP proxy) and the
pane puts it back inside the webview's focus event, and again when the press
ends. Offscreen pages keep their native focus.
* Require exact-commit PR checks before agent merges
* Give full PR checks a unique required status context
* Validate required context ownership on every pull request
Older requested refs predate the security addon builder, so the desktop
template CI job failed trying to run a script that isn't there. Gate the
build, keep, and collect steps on that script existing in the checkout.
Add a test that pins the gating for both cases.
Also drop an unused getAppEnvironment import in orcad-entry.ts.
* [CI] Reduce redundant test work and import existing helpers directly
* [CI] Preserve provider guards and pin the mobile browser test core
* [CI] Fix CI test setup and verify retained contracts
* [CI] Compare native media chunks without boxed byte arrays
* [CI] Detect shifted native media reads with position-sensitive bytes
* feat(windows): add administrator PowerShell terminal setting
* fix(settings): localize administrator terminal search keywords
* fix(windows): restrict elevated terminal host to Orca's runtime and requesting account
* fix(windows): harden administrator terminal routing, inventory and restore
* fix(build): build the Windows agent hook when electron-builder packages Windows
* fix(build): build the packaged Windows agent hook for the target architecture
* fix(windows): accept the development terminal runtime and test real elevation in CI
* feat(windows): show administrator status and restart instructions
* fix(settings): localize administrator mode search keywords
* test(settings): isolate the desktop permission section in shell picker tests
* Add Windows administrator restart and simplify Terminal settings
* Keep administrator restart silent and settings actions within narrow panes
* Keep Windows shell choices and permission guides usable at minimum width
* Prevent later restarts from replaying administrator terminal reset
* Fix CI route resolution and transient Windows fixture cleanup
* ci(relay): use the runner's preinstalled gcloud instead of downloading the SDK
dl.google.com downloads of google-cloud-sdk time out on the runners, failing every relay
operation at setup-gcloud. skip_install keeps the WIF cred-file login.
* ci: drop the temporary gcloud skip_install proof workflow
* ci(wsl): real WSL2 lane for orcad deploy, bridge, liveness and holder (WSL-via-orcad C10)
* ci(wsl): upload the template's dot-directories; save the rootfs cache on failure
* ci(wsl): update the runner's WSL to 3.0.1+ before asserting WSLInterop survives
* test(wsl): let a released holder settle before asserting it exited
* ci(wsl): install a pinned, cached WSL 3.0.1 MSI instead of wsl --update
* test(wsl): keep the lane's survival seconds typed in its assertion message
On a validated AppImage the desktop's daemon launch first runs <generation>/resources/node-runtime/bin/node
on the generation's daemon-entry.js when the extracted copy for the file at $APPIMAGE is complete, carries
the bundled Node and the entry, and is the running release. It then falls back to the AppImage's own mount
and the current mount, logging every fallback. The copy rung records $APPIMAGE/<entry> like the own-mount
rung, so either daemon is adopted. Generation leases recognize the bundled Node. macOS, Windows, deb and
rpm never reach it.
Co-authored-by: m4air <m4air@Mac.localdomain>
* ci: run mobile runtime test groups on separate runners
* ci: use a lightweight runner for the mobile result gate
* ci: distribute Bun tests across both mobile runners
* ci: reuse mobile compiler state without adding runner queues
* test: verify eviction of source variants in one cache bucket
* test: baseline existing serializer limits in the capacity capture
* fix(windows): restrict secure files in-process instead of spawning icacls
Every secure write on Windows spawned icacls.exe five times on the calling
thread (the Electron main thread for most stores): verify, /reset,
/inheritance:r /grant:r, verify, verify. Process creation under endpoint
security costs ~0.6 s each, so each write froze the UI for seconds (#26027).
The /reset + /inheritance:r pair is also what behavioural EDR scores as
ransomware tradecraft (#25583).
Add native/windows-security (@orca/windows-security): a small N-API addon
over advapi32 that reads and writes DACLs in-process. It exposes the token
user SID, a DACL read as SDDL, a DACL write (sync and on the thread pool),
a create-with-DACL file write, and an additive grant. Policy and
verification stay in TypeScript.
- writeSecureFile creates each temp file already carrying the protected
user/SYSTEM/Administrators DACL (CreateFileW + SECURITY_ATTRIBUTES), reads
it back once, and renames it. No ACL is modified on the write path and no
process starts. Before: 5-7 spawns, 94-138 ms per write. After: 0 spawns,
~0.5 ms.
- A secure directory is verified once per process. It is rewritten only
when verification fails, on the thread pool, because the write walks
every descendant (~2 s on a 29k-entry userData).
- The verifier follows OpenSSH's rule: only the user, SYSTEM and
Administrators may be granted anything, and the path must be protected
(or, for a file, inherit from a secure parent). DACLs written by the
icacls-era hardening pass unchanged, so there is no migration.
- If the addon cannot load, the write still happens and reports
unrestricted, logged once. There is no icacls or PowerShell fallback.
- grantDirAcl, the startup userData grant, and the install-dir probe and
package repair use the same addon, so whoami.exe and icacls.exe are no
longer spawned anywhere at runtime.
- orcad's Windows slots ship the addon beside their bundles (built per
arch, PE-machine checked). Its data-root privacy check fails closed, so
without the addon a Windows orcad would not start.
Fixes#26027Fixes#25583
* refactor(secure-file): harden existing Windows files through the sync lane
The read path called restrictWindowsPathSync directly and recorded success
itself; the existing sync lane already does both. One call site fewer.
* refactor(windows-security): one load, one rule, no fallthrough (review #28135)
- The descriptor parser is anchored, so a conditional ACE (its body holds
parentheses) or anything else it cannot read fails closed instead of
verifying the ACEs before it.
- findSecureDaclViolation is one flat rule: protected, allow rules only for
the user, SYSTEM and Administrators (OpenSSH's ACE rule), and full
control for the user. The inherits-from-a-secure-parent exception is gone:
it accepted exactly what loosening the parent reaches.
- The addon and the user SID load once, behind one wrapper that adds the
extended-length prefix to every path and runs async DACL writes to one
path in order, so a startup grant cannot land after a re-lock and restore
the broader DACL.
- With the addon loaded, a failed born-restricted create throws with the
matching Node error code instead of falling back to a plain write.
- A grant leaves a NULL DACL untouched instead of turning 'everyone' into
'only this SID'. WriteFile runs once and fails on a short write.
- The artifact create-intent store writes through writeSecureFile and drops
its own temp writer, directory LRU and synchronous directory re-lock.
- Test seams the global fake makes redundant are gone.
* fix(packaging): fail a Windows build whose workspace addon binary is missing or foreign-arch
The @orca/windows-* workspace links exist on every host, so their presence
proved nothing: a missing or wrong-architecture .node only surfaced at
runtime, as unrestricted secure files or an orcad that refuses to start.
beforePack now reads each addon's PE machine against the target arch.
* refactor(windows-security): prune what the native ACL left dead (review #28135)
- Sync-spawn ratchet: hardenSecurePath and the sync-flag exemption it alone
used are gone, and the advice and baseline header no longer say secure
writers run icacls.
- The install-dir probe reads ACEs through parseSddlDacl, so an ACE it
cannot parse fails the whole reading closed instead of being skipped; AC
is a resolved alias there now.
- The packaging check reads the addon binary through node_modules, the copy
that ships.
- No winCode-to-errno table: nothing reads .code off a failed secure write.
- The load memo is not mutated; secure-path keeps its own log-once flag.
- grantInstallDirPackageAcl is a plain function, and secure-file restricts
a Windows directory and a POSIX path directly instead of through one
two-platform helper.
* fix(windows-security): resolve LA in real-ACL tests; tear a file whose close fails
- The GitHub Windows runner is the built-in Administrator (RID 500), whose
SID reads back as LA. Production already resolves it through
localDomainSidOf; the win32 test helpers parsed without it. One shared
readDaclOf resolves aliases for every real-ACL suite, and the verifier's
unit test covers an LA-aliased directory and LA as a stranger.
- createFileWithDacl treats a failed CloseHandle like a failed write (lost
data on a network volume) and deletes the torn file.
- Drop a duplicate test reset and a typo.
* fix(windows-security): load only from inside the app; read DACLs as structure (review #28135)
- Loader: orcad's staged binary first, then the workspace package only if
its real path is inside the app (process.resourcesPath, or this
checkout). A bare specifier otherwise walks every parent up to
C:\node_modules, which any local user may create, and orcad holds
credentials.
- The addon reads a DACL as { protected, aces: [{ type, flags, mask, sid }] }
via GetAce, with a SID only for plain allow/deny ACEs and null for a NULL
DACL. findSecureDaclViolation and the install-dir probe check that
structure, so conditional, callback and object ACEs fail closed. The SDDL
parser, its alias table, LA/localDomainSid resolution and getDaclSddl are
gone; SDDL is only the write format.
- Install-dir tree pass: files flagless as before, but a directory only when
its own DACL is protected (inheritably). Re-granting inheriting
directories re-propagated each subtree (3.2 s for 1,093 directories). The
root is no longer granted, or counted, twice. The two passes are plain
functions behind a { root, tree } seam.
- One namespacing (path.win32.toNamespacedPath) at the addon boundary, also
the per-path ordering key; the comment states the real sync-grant
invariant.
- restrictWindowsDirectory(dir): Promise<boolean> replaces the callback
lane; the reporter is guarded once, so the 'settle' stage is gone.
- Sync-spawn ratchet: the secure writers no longer spawn or block, so they
leave the gate (109 -> 61 uses).
- Errors name the Win32 call and code only; a failed
GetSecurityDescriptorControl fails the write instead of defaulting to
unprotected.
* test(secure-file): assert the durable POSIX write's outcome, not the old chmod order
writeSecureFile now chmods the temp file to 0o600 before fsyncing it, so
the fsync also covers the mode change and the file is owner-writable when
opened. Assert what matters under umask 0o200: the write succeeds, the
temp file is fsynced read-only, and the published file is exactly 0o600.
* fix(windows-security): namespace paths with the host's toNamespacedPath
path.win32.toNamespacedPath rewrote POSIX temp paths (/tmp/x -> \tmp\x) in the
Linux runs of the Windows-logic unit tests, so the fake addon wrote to the wrong
place. The addon only runs on Windows, where the host function is the win32 one;
elsewhere it is the identity. The long-path assertion runs on Windows only.
* test(windows-security): assert the install-dir long-path prefix on Windows only
The prefix comes from the host's toNamespacedPath, which is the identity off
Windows; the Linux run of this suite expected the win32 escape.
* fix(windows-user-data-acl): grant the root even when its children cannot be listed (review #28135)
A readdir failure on userData aborted the whole grant, so the root grant
that could repair a DACL refusing the listing was never attempted. The
listing failure is now recorded, the root is still granted, and the
marker stays withheld so the next launch retries.
* test(windows-security): wait for relock propagation, accept Entra ID SIDs (review #28135)
- The large-relock test waited only for the parent to turn protected,
which happens before propagation reaches the children, then read a child.
It now waits on the parent and both ends of the child range.
- The token-user test rejected S-1-12-1 (Entra ID) accounts, failing the
suite on Entra-joined dev machines.
---------
Co-authored-by: Orca Worker <orca-worker@localhost>
* build(linux): carry the pinned Node inside the AppImage so orca serve's slot works offline
Linux packaging places the pinned glibc Node at resources/node-runtime/bin/node before the
glibc-floor gate, proves it serves one PTY with the packaged daemon entry, and deb/rpm exclude
it through fpm. orca serve's orcad slot copies that Node (hash-checked) instead of downloading.
* test(serve): type the bundled Node fixture
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
Overlap mobile typechecks with tests and avoid repeated filesystem probes and unused device substitutes in recording replays. Keep state isolated between loaders, give exhaustive snapshot cases an explicit execution deadline, and retire obsolete locale keys that blocked repository validation.
* Remove repeated daemon smoke builds and idle compiler slots
* Test compiler scheduling through its process interface
* Keep preview component below the existing line limit with a type-only split
* perf(claude): run the Windows Claude hook as a native orca-hook.exe
Each Claude hook event on Windows started cmd.exe (plus the user's cmd
AutoRun) and then curl.exe on top of the agent's own shell. A small signed
Rust binary now does the same synchronous POST in one process, under Git
Bash, PowerShell and direct CreateProcess alike.
- native/windows-agent-hook: std-only crate, same contract as the .cmd hook
(prints {}, env guards before stdin, endpoint re-read, 0.5s/1.5s caps,
waits for the listener reply, always exit 0). Its installed file name
selects the agent profile, so the registration stays a bare path.
- Packaged as resources/bin/orca-hook.exe (SignPath signs it with the other
inner PEs); afterPack fails on a missing or wrong-machine binary.
- Claude install copies it verbatim to ~/.orca/agent-hooks/claude-hook.exe
(temp + rename, running copy moved aside) and registers it; it falls back
to the .cmd hook when the bundle is missing, unsigned or cannot be copied.
Only Claude opts in, via windowsNativeHookExecutable.
- The managed-command matcher sweeps .exe entries alongside .cmd/.ps1/.sh.
- The CLI launcher and hook builds share windows-rust-executable-build.mjs,
which now also checks the PE machine.
Refs #20913
* fix(build): publish native Windows binaries atomically so parallel builds cannot collide
The Windows CI lane runs every orca-hook.exe suite in one vitest
invocation, each building through build-windows-agent-hook.mjs. The build
copied straight onto native/windows-agent-hook/.build/orca-hook.exe, and
CopyFile opens its destination unshared, so a second build (or a scanner
still holding the fresh image) failed with EBUSY.
- windows-rust-executable-build.mjs skips the publish when the output
already has the built bytes, otherwise stages a per-process temp file and
renames it over the output, retrying briefly while another handle holds it.
- The test fixture hands each suite a private copy of the build output, so
no suite reads or starts the shared file while another build replaces it.
Refs #20913
* refactor(claude): simplify the native Windows hook to one Claude binary
Review follow-up on the native hook (#20913): the same behaviour with less
machinery.
- The binary is built, shipped and installed as claude-hook.exe, so the
installed name matches its PE OriginalFilename. It is Claude-only, so the
file-name profile table and the per-agent opt-in flag are gone; the
registration is gated by the existing Claude-only usesWindowsEntry check.
- Install and refresh reuse writeManagedScript / refreshManagedScriptIfPresent
(now accepting bytes): temp file, rename, skip when identical. A copy whose
hook is still running cannot be replaced; install then registers the .cmd
hook and refresh keeps the old build until a later start, instead of the
rename-aside and *.retired sweep.
- The install-time Authenticode-presence check is dropped. Release CI now
requires resources\bin\claude-hook.exe to be signed (release-cut and the
signing rehearsal), next to resources\bin\orca.exe.
- getStatus counts either Windows form through findManagedEventHookForms in
hook-settings.ts, replacing managed-event-hook-status.ts.
- Rust: plain Claude constants, one endpoint-file lookup (last assignment
wins, as under `call`), one request write and a deadline-bounded drain.
Guard order and form encoding are unchanged.
- Build: PR CI builds the hook once before the Windows tests, so the suites
only read it; the launcher publish goes back to copyFileSync and the retry
loop and its test are gone. The PE-machine check lives only in afterPack.
Cargo.lock joins both crates' build fingerprints, and the launcher's alias
exports are removed.
Refs #20913
* test(ci): run the managed hook stdin-lifecycle suite in the Windows lane
Its claude-hook.exe case (and the other Windows-only cases) are gated per
test, so the lane-registration guard did not require the file and it ran
only on Linux, where those cases skip. Register it in both Windows lists.
Refs #20913
* feat(agent-hooks): ship the native hook as orca-agent-hook.exe <agent>
The native Windows hook (#20913) becomes one general binary that stays
Claude-only in behaviour.
- Named orca-agent-hook.exe everywhere: build output, PE OriginalFilename
and InternalName, resources\bin, the afterPack check, the installed copy
in ~/.orca/agent-hooks, and the release signed-target lists.
- It takes the agent as its first argument and Claude registers
`<path> claude`. The CLAUDE_JOB_DIR exit and the Grok/Devin drain belong
to that agent; an unknown or missing agent answers {} without reading
stdin. The managed-command matcher owns `orca-agent-hook.exe claude` and
still owns the old claude-hook.exe entry, so installs migrate; install
deletes a leftover claude-hook.exe when nothing runs it.
- A copy still running from the previous build no longer forces the .cmd
fallback. Identical bytes are not rewritten; otherwise the temp file is
renamed over the target, and when Windows refuses because the image is
mapped, the running copy is renamed to orca-agent-hook.exe.old first.
Install deletes a stale .old; only when that copy is also still running
does the install fall back to .cmd. writeManagedScript and
refreshManagedScriptIfPresent take the aside path, sharing one helper.
Refs #20913
* fix(agent-hooks): roll back a failed aside rename and keep an installed hook exe
Design-review follow-up on the native hook (#20913).
- If the running copy was moved to orca-agent-hook.exe.old but the new
file then could not take its name, the copy is renamed back before the
error is rethrown, so settings.json never points at a missing exe. The
rename helper now owns the `.old` name, next to the comment that
explains it; a mocked-fs unit test covers both the sync and async paths.
- A failed write, or a build with no bundled exe, keeps registering a copy
that is already installed (every build knows `claude`), so a dev checkout
and an installed build no longer flip settings.json between forms.
- The claude-hook.exe migration is gone: no release ever shipped that
name, so the legacy sweep, the `.exe` matcher needle and their test are
removed.
Refs #20913
* fix(agent-hooks): derive the native hook's agent with win32 path rules
getWindowsNativeHookAgent took the host's basename of the managed script
path, so on a POSIX host (Linux CI) a Windows path was not split and the
registered command became `orca-agent-hook.exe C:\...\claude`. It now takes
the path and applies path.win32.basename itself. The install destination
and the `.old` aside stay on host paths: they name files the host opens.
Refs #20913
---------
Co-authored-by: Orca Worker <orca-worker@localhost>
* fix(claude): register a statusLine Windows PowerShell can run
Claude runs the statusLine under Git Bash when present and otherwise under
PowerShell, which cannot parse the POSIX runtime-home command Orca registered.
On Windows without Git Bash the statusline never posted rate_limits, so the
live Claude usage feed went dark. Register the same shell-neutral .cmd
invocation the lifecycle hooks use; the old form is still recognised as
Orca-owned and replaced, and a user's own statusLine is never touched.
* fix(claude): keep the statusLine free of a per-tick PowerShell launch
A profile path the bare form cannot carry (a space, non-ASCII, quotes) fell
back to the encoded PowerShell launcher, which would start a hidden PowerShell
on every statusline tick and print "{}" if the script went missing. Register
the bare .cmd path only when it is cmd-safe and keep the runtime-home form
otherwise, so Git Bash users on such paths still run the .cmd directly.
Fold the registration tests into claude-status-line-install.test.ts and add a
real Git Bash run for a profile path with a space.
* test(claude): pair the stubbed win32 platform with win32 basename
The Windows statusLine case fed a Windows path to getManagedCommand on a
Linux runner, whose posix basename does not split backslashes. Production
only takes that branch on real Windows, where node:path is path.win32.
---------
Co-authored-by: Orca Worker <orca-worker@localhost>
* fix(daemon): verify daemon PID identity from the native Windows process table
The Windows daemon PID check forked powershell.exe Get-CimInstance through
node:child_process with no windowsHide, flashing a console and costing
~850ms per check. Read the command line and creation time from the shared
windows-process-table reader instead, and route the POSIX ps queries
through @orca/process-host.
Refs #27390
* refactor(daemon): drop the ps migration and trim the PID identity tests
Keep #27703 to the PowerShell removal: restore the macOS/Linux ps paths,
their tests and the process-host baseline entry. Collapse the mocked
Windows identity cases into one table, keep only the real-addon native
case, and move the creation-time fallback beside queryWindowsProcess.
---------
Co-authored-by: Orca Worker <orca-worker@localhost>
Preserve all 52 Markdown cases while consolidating one file and importing existing direct owners. Advance editor debounce windows through public selective timers, exclude unrelated lint tests from Performance PR triggers, and remove a pane assertion case entailed by retained coverage.
* ci: ratchet synchronous child-process launches in main and shared code
The Electron main process freezes every window while a synchronous child
process runs, and on Windows each launch is slow because antivirus scans
it. Nothing stopped new synchronous launches from landing in main-process
code, and the spawn observer behind ORCA_MAIN_THREAD_DIAGNOSTICS only saw
async spawns, so the worst blocks were invisible to it.
Ratchet: check:main-sync-spawn-ratchet parses production src/main and
src/shared files (test-only paths excluded) and fails when a file not in
config/main-sync-spawn-baseline.txt reaches execFileSync, spawnSync or
execSync from child_process, runProcessSync from @orca/process-host, or
one of the synchronous secure-file writers (they run icacls on Windows).
Named, aliased, namespace, default, require() and import() forms count.
A baseline entry whose file no longer launches synchronously fails as
stale, so the list can only shrink. It runs in lint and in the PR
static-analysis job next to the process-host import check.
Observer: runProcessSync now reports to the spawn observer, timing the
whole call (that is how long the thread was stuck) and reporting from a
finally so failing, throwing and timed-out children are counted too.
Runtime behavior is unchanged except that the diagnostics observer now
sees synchronous launches; with no observer registered the sync path
pays one extra branch.
* ci: count sync launches per file and gate the repo's sync wrappers
The sync-spawn ratchet only tracked which files launched synchronously, so a
file already on the list (wsl.ts, win32-utils.ts, git-exec-file.ts) could add
more launches unnoticed, and new callers of the repo's own synchronous
wrappers passed CI.
The baseline now stores a per-file count of uses, in the same format as the
focus-read ratchet: a rise or a new file fails, a lower or zero count fails
until `pnpm check:main-sync-spawn-ratchet --prune` lowers it, and --prune
never raises an entry. Every value reference to a gated function counts
(call, hand-off, alias and its uses, re-export); imports alone count nothing.
Gated wrappers: gitExecFileSync (and the git/runner re-export),
restrictWindowsPathSync, hardenSecurePath when called with sync: true,
grantDirAcl, getWslHome, listWslDistros, wslUncDirectoryExists,
isWslAvailable (and the wsl.ts re-export), isPwshAvailable. Their 18
current caller files join the baseline.
Failure messages now give advice per family: the async process-host API for
child-process launches, the async twin for wrappers, and for the secure
writers a note that no async secure writer exists yet, so the sync one stays
and must never become a plain write (that drops the Windows ACL hardening).
Also refresh two stale blockMs descriptions and drop a no-op line from the
spawn-observer "stays silent" test.
* ci: count hardenSecurePath unless visibly async, and tighten ratchet tests
hardenSecurePath was counted only when `{ sync: true }` was written inline,
so moving the options into a variable or a spread dropped the count and the
check then asked for a prune. It now counts every use except a call with no
options or a spread-free object literal whose `sync` is absent or false.
Also: resolve backslash paths in the importing file as well as the
specifier; still visit heritage and instantiation expressions
(`extends mixin(fn)`, `fn<T>`); state the reference-only counting limit;
and point the secure-writer advice at raising this file's baseline count.
Tests cover the hardenSecurePath shapes, the alias chains that need the
fixpoint loop, and a prune that must not raise a grown row.
* test(ci): pin the main sync-spawn ratchet background step
* fix: count secure hardening calls with spread arguments