* chore(lint): remove disable directives that suppress nothing
Each was reported unused by the oxlint config that owns its rule (root,
casting, react-doctor, anti-slop, native-plugins), including 17 file-level
react-doctor blanket disables whose rule no longer fires. An unused
react-hooks disable was masking a react/purity finding in
useAgentBucketCounts (compiler rules skip functions with hook
suppressions); suppress that one explicitly where the code explains it.
* test(tab-bar): use typed tab/file fixtures instead of @ts-expect-error partials
* chore(lint): fix misc suppressed lint violations instead of disabling
- consistent-type-imports: the "vi.importActual requires inline import()"
rationale was wrong; use typed vi.mock(import(...), importOriginal) or a
type-only namespace import.
- no-control-regex: source-control commit/push failure summaries, shell PATH
hydration, and ipynb ANSI parsing reuse the shared ansi-escape-sequences
stripper and control-character pattern instead of private copies.
- no-explicit-any / no-useless-spread / no-this-alias: typed `this` in
bridge test mocks, drop blanket file-level disables, drop a this-alias.
* refactor(main): replace avoidable type assertions with checked narrowing
17 production cast suppressions in main/wsl-guest removed: untrusted IPC
and RPC payloads (Jira issue update, WSL guest plugin install, handshake
type) now narrow through isRecord/type guards and build typed results;
redundant casts dropped; hand-rolled object checks and errno casts use the
existing isRecord / hasErrorCode helpers.
* refactor(renderer,mobile): replace avoidable type assertions with checked types
13 production cast suppressions removed: RPC failures read through the
`ok` discriminant instead of casting to RpcFailure; react-native-web host
refs narrowed with instanceof HTMLElement; redundant reply/diff casts
become checked annotations; the TUI agent param uses z.custom(isTuiAgent)
like its sibling schemas; the agent-status slice cast that hid missing
actions is gone so the creator's return type checks the whole slice.
* chore(lint): drop dead tiptap wrapper; scope anti-slop mocking rule to test support
- safe-react-node-view-renderer patched handleSelectionUpdate, which
@tiptap/react 3.31.3 no longer has, so it always returned early. Use the
stock ReactNodeViewRenderer.
- The anti-slop test override only matched *.test.{ts,tsx}, forcing
file-level no-module-mocking disables in test harnesses and fixtures.
Match *-test-harness, *-test-fixture(s), .test-fixture and *.test.mjs,
then drop those disables and the now-redundant per-file exemptions.
* test: replace repeated cast suppressions with typed fixtures (main/shared/e2e)
Net -119 cast directives. Typed fixtures replace recurring casts:
unusedSshConnection/unusedOrcadHostTransport placeholders, a typed
readPersistedState harness read (exposed a test treating optional
httpProxyUrl as always set), real Response objects in fetch spies, an
updater main-window fixture, a CDP webContents adapter, one shared
releaseModuleExport for cross-version tests, and requireOverlayDir in
place of `as string` on possibly-undefined overlay paths.
* fix(runtime): validate loaded device rows and host scope instead of casting
Two cast suppressions claimed checks that did not happen:
- device-registry-file-read: "normalizeLoadedDeviceRow re-validates every
field" was false; deviceId, token, name and timestamps were never
checked. Rows without an identity are now dropped and the file is set
aside through the existing corrupt-file path (registry reads as
doubtful, so no revocation verdicts); a non-string token is stripped
so the row stays listed but cannot authenticate.
- client-ui: "only optionality differs" was false; any string reached
persisted workspaceHostScope. Narrow it with isWorkspaceHostScope and
ignore unknown scopes (wire schema unchanged for mixed versions). The
remaining cast now states what it actually covers.
* test(runtime,ipc): drop redundant cast suppressions; typed runtime store fixture
-98 cast directives across runtime/ipc tests: 61 casts that typecheck
without the assertion; a typed vi.fn for scoped invalidation; a
partialRuntimeStore fixture in place of `store as never` for
OrcaRuntimeService; typed worker terminal/authority builders in the
incarnation-fallback suite (the old stub lacked runtimeId, ptyId,
worktreeId and launchTokenHash) plus removal of stacked duplicate
directives there.
* chore(lint): typed vi.mock imports and explicit listener snapshot in shared-bucket tests
Remaining files from the misc-rule pass that overlapped other buckets:
codex-accounts mocks use the typed vi.mock(import(...)) form instead of
a consistent-type-imports disable; the daemon late-exit fixture copies
its listener set with Array.from instead of a no-useless-spread disable.
* refactor(github,mobile): narrow routing settings type; drop remaining avoidable casts
- work-item-routing cast a one-field object to AppState['settings'] under
a claim that readers only consume activeRuntimeEnvironmentId. Return the
existing RuntimeFocusSettings pick instead and narrow the cache-key
chain's parameters, so the compiler enforces that claim.
- mobile file preview reads refusals through the RpcResponse `ok`
discriminant rather than a cast.
- terminal document host-notify accepts `unknown` errors, removing the
message-bridge cast.
- native chat readability drops a redundant repo-list cast.
* test(renderer): replace repeated cast suppressions with typed fixtures
-140 cast directives (and ~150 unsuppressed casts of the same shapes):
- mockPanePtyArgs owns the connectPanePty fixture casts, so a wrong
argument order or fixture is now a type error; drop binding casts for
members PanePtyBinding already declares.
- Seed AppState tab rows with the typed makeTerminalTab; this exposed a
split direction 'row' that is not a TerminalPaneSplitDirection.
- Partial GlobalSettings use getDefaultSettings overrides; Jira-only
stores use createTestStore; runtime environment rows/statuses come from
a typed paired-runtime-environment fixture.
* chore(lint): drop no-module-mocking disable now covered by the test-fixture glob
* test(e2e): fix all 221 grandfathered e2e type errors; baseline to zero
The e2e typecheck baseline grandfathered 221 errors in 119 files. About a
third were tests drifted from the current API, several of which would
fail or mislead at runtime:
- terminal-shortcuts called a nonexistent xterm buffer method;
- the daemon-lifecycle fake lacked terminateOwnedTree, hidden by importing
its type from a module that does not export it;
- arguments in the wrong position (listProcesses(hostId, opts), a dropped
pty.write inputKind, an extra toWebTerminalSurfaceTabId argument);
- removed enum values ('drawer', 'idle', 'editor',
'session-insecure-origin') and a launch_source the telemetry schema
rejects;
- missing required fields (turnScope, childWorkOpen, providerIdentity,
reviewLookupOutcome, runtimeServiceAccount behind an `as RelayConfig`);
- dead reads (tab.customTitle, bucket.targets, changedPixels).
Also lifts variables that narrowed to `never` after poll-callback
assignment, and adds a typed xterm render-dimensions accessor. Includes
the cross-version suites' shared releaseModuleExport adoption.
* chore(typecheck): gate the now-clean e2e typecheck in local lint
The e2e baseline is empty, so the ratchet now rejects any tests/ type
error. CI already runs it via run-ratchets; add it to pnpm lint's
explicit ratchet list and replace the stale 'not yet clean' note.
* test(mobile): typecheck every mobile test; baseline 105 files -> 0
mobile/tests-typecheck-baseline.txt grandfathered 105 test files (531
errors) whose types CI never checked. Contract changes had landed without
these tests following, and type pins in them silently checked nothing.
- Root cause: salvagedOptional (src/shared/zod-salvage.ts) cast away
zod's optional marker, so every reply type built on it claimed
salvaged fields were always present. Return a real ZodOptional;
runtime is unchanged (the outer optional short-circuits undefined).
- Production now takes the port it uses: RpcOperationSender instead of
RpcClient for send-only callers, a narrow fetch signature, and only the
request fields push-receive reads.
- findAllByHostType/findByHostType replace `'Text' as never` lookups;
stubRpcClient gives tests a typed RpcClient.
- Real test bugs: settings-host-client-lifecycle "relay" hosts were
direct hosts (relayHostId no longer exists); two toEqualTypeOf pins
could never hold; a recovery hook ran with an undefined scope key;
a create-PR test cast to a never-imported type; "no URL" assertions
passed on null; plus stale required props, args and enum values.
* test(e2e): parse workerStart result instead of casting it
* test(runtime): typed partial runtime stores with production-shaped fixture data
Route every partial store passed to OrcaRuntimeService through
partialRuntimeStore with typed row builders, removing 60 cast directives
that hid fixture data production never produces:
- paired-client-navigation-test-harness keyed setWorktreeMeta writes by
`[object Object]` (it took an id where the store takes a target), so
reads never saw them;
- invalid worktree lineage origin/capture and incomplete workspace lineage;
- getWorkspaceSession returning null where the store returns the default
session; getGitHubCache returning undefined;
- orcad migration states missing migrationId/manifest/receipt, with a
toEqual pinned to that incomplete shape;
- partial repos, metadata, folders, project groups and notification rows.
* refactor(renderer): resolve suppressed React hook lint instead of disabling it
109 of 187 react-hooks/react-doctor suppressions fixed:
- ~95 exhaustive-deps disables only hid stable refs and setters passed
through controller objects; list them (identity never changes, so
nothing re-runs), plus a few real stale closures (tabId/worktreeId).
- Effects keyed on one value that read the latest props now use
useEffectEvent (WebConnect, skill freshness, Bitbucket dialog seeding,
Landing preflight, automation catalog, structured-session recovery,
file explorer).
- The conditional layout-owner hook is selected once at module load from
the build constant instead of a rules-of-hooks disable that also hid
the whole controller from compiler and react-doctor rules.
- A hook disable was masking a react/purity Date.now() in the palette's
recent-tabs memo; sample it when the snapshot is captured.
- Three identical debounced-search effects share
useTaskSearchDebouncedApply; layout snapshot merge helpers move out to
stay under max-lines.
Remaining suppressions are mount-once resources, invalidation epochs,
deliberately keyed fields, and test harnesses that mock the dispatcher.
* chore(ratchet): exempt test harnesses and fixtures from the focus-read ratchet
The ratchet skipped *.test-fixture.ts but scanned *-test-harness,
*-test-fixture(s) and *.test-support files. Those seed settings with
getDefaultSettings (which the ratchet discovers as a focus reader) and
never route host work, so 17 baseline entries were false positives, and
replacing a test's `settings: {...} as never` with typed defaults tripped
it. Match the repo's test-support names and prune (which also drops one
stale entry), and rename the frame-ordering harness to *-test-harness like
its siblings. A blanket "-harness" match would also exempt
e2e-terminal-pty-harness, which production imports, so the names stay
explicit.
* test: merge duplicate shared/constants imports
* revert(file-explorer): restore original tree load effects pending SSH e2e investigation
* test(runtime): drop RuntimeStore cast now that the fixture is typed
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
daemon-relocation-spike
A throwaway probe that answers one empirical question for Phase 1 of the Windows update-survival work:
What is the minimal set of files that must be copied out of a packaged
win-unpackedbuild so that a copiedOrca.exe— run withELECTRON_RUN_AS_NODE=1from a directory outside the install dir — can: (a) start the terminal daemon and signal ready over IPC, (b) spawn a real ConPTYnode-ptysession, write input, and read output back, (c) do all of that while holding no open file handles into the app/install dir, so an NSIS update could delete the original install.
Why this matters
The daemon is fork()ed from the app's own Electron binary (Orca.exe) with
ELECTRON_RUN_AS_NODE=1, from the install directory. On a Windows update,
electron-builder's NSIS installer (a) runs uninstallOldVersion (deletes the
registered install's files) and (b) CHECK_APP_RUNNING force-closes every
process whose image path is under $INSTDIR. So the daemon dies and its held
file locks can break the update.
The Phase 1 fix copies the daemon's whole file closure out of the install
dir (into %LOCALAPPDATA%/userData) and forks the daemon from the copy, so
its image + all loaded modules live outside $INSTDIR. This spike measures how
small that copy can be while still working.
We keep the Electron binary run as node (not a stock node.exe): a prior
attempt (#7473, reverted) switched to stock node and caused Windows-console
flashing (stock node lacks Electron's kHideConsoleWindows) plus asar breakage.
This spike does not reintroduce stock node.
Usage
# Real run (Windows, needs a packaged win-unpacked build):
node tests/tools/daemon-relocation-spike/spike.mjs \
--app-dir <path-to-win-unpacked> \
--work-dir <scratch-dir> \
[--tier full|no-gpu|minimal] \
[--keep-work-dir]
# Offline logic validation (any OS, no build, no launch):
node tests/tools/daemon-relocation-spike/spike.mjs --selftest
Exit code is 0 only when the run PASSES: daemon ready, PTY echo
round-trips the nonce, the daemon's main module is the copied Orca.exe, and
no loaded module resolves under --app-dir.
Tiers (defined as data in tier-file-set.mjs)
Every tier includes the irreducible core: Orca.exe, icudtl.dat, both V8
snapshot blobs (snapshot_blob.bin, v8_context_snapshot.bin), the daemon
bundle (out/main/daemon-entry.js + chunks/ + out/package.json), and the
whole node-pty package (native conpty.node + the sibling conpty/ runtime
dir holding conpty.dll + OpenConsole.exe).
Tiers differ only in which top-level *.dll files they carry:
| Tier | Top-level DLLs |
|---|---|
full |
all top-level *.dll |
no-gpu |
all except GPU/render DLLs (libEGL, libGLESv2, vk_swiftshader, vulkan-1, d3dcompiler_47); keeps ffmpeg.dll |
minimal |
none (exe + data blobs + daemon bundle + node-pty only) |
Trimming further is a config change (edit TIER_DEFINITIONS / GPU_DLLS), not
a code change.
How node-pty's native + ConPTY runtime is handled
The whole node-pty package tree is copied, and the entire win-unpacked
layout is mirrored verbatim (every copy destination is relative to the
win-unpacked root, not the asar-unpacked root). This matters because node-pty is
packaged at resources/node_modules/node-pty — a sibling of
app.asar.unpacked, not under it (see
config/packaged-runtime-node-modules.cjs). Mirroring the full layout preserves
two resolutions from the relocated path:
- The daemon require-closure resolves
require('node-pty')by walking parent dirs up from the mirroredresources/app.asar.unpacked/out/main/daemon-entry.js, which passes throughresources/and findsresources/node_modules/node-pty— exactly as in the packaged app. - node-pty's own native loader resolves
conpty.nodefrombuild/Release(orprebuilds/win32-<arch>) relative to node-pty's own__dirname, and node-pty's Windows addon loadsconpty.dllfrom<dir-of-conpty.node>/conpty/and spawnsOpenConsole.exefrom beside it. Copying the tree verbatim keeps all three side-by-side.
ORCA_NODE_PTY_NATIVE_DIR
The reverted #7421 added a node-pty patch that reads
ORCA_NODE_PTY_NATIVE_DIR to override the native dir. The current branch's
config/patches/node-pty@1.1.0.patch does NOT contain that override — it was
reverted. The spike therefore relies on layout preservation (copying the
node-pty tree at its default relative path) rather than the env override. The
spike still sets ORCA_NODE_PTY_NATIVE_DIR to the relocated native dir so it
keeps working if pointed at a build that carries the patch, but on this branch
the var is inert.
Implication for the real Phase 1 implementation: if the production copy does
NOT preserve node-pty at the path its loader resolves by default (e.g. if the
daemon-entry is relocated without the sibling node_modules/node-pty), the impl
will need to re-add the ORCA_NODE_PTY_NATIVE_DIR patch from #7421. If it
mirrors the layout as this spike does, the patch is not strictly required —
though re-adding it is the more robust choice.
The handshake / client
ndjson-client.mjs is a small standalone NDJSON client (no electron/src
imports) that mirrors src/main/daemon/daemon-server.ts:
- Read the token the server writes to the token file after it begins listening.
- Open a control socket, send
hello {role:'control'}, await{type:'hello', ok:true}. - Open a stream socket with the same
clientId, sendhello {role:'stream'}. createOrAttachon control, thenwriteecho SPIKE-OK-<nonce>\r\n, and readdataevents on the stream socket until the nonce appears alone at line start (executed output, distinct from the echoed input line).
PROTOCOL_VERSION is read at runtime from src/main/daemon/types.ts so the
client never drifts from the daemon.
The handle probe
loaded-modules.ps1 (via loaded-module-probe.mjs) runs
Get-Process -Id <pid> and enumerates .Modules[].FileName. Any module path
under --app-dir is a lock risk (the installer cannot replace a file a live
process maps), so a passing relocation must show zero. It also asserts the
process's main module is the copied Orca.exe, not the install-dir one.
Loaded DLLs are the lock-critical set. Data files (icudtl.dat, asar) are not
memory-mapped as modules, so this probe does not enumerate them — the copy plan
handles those by construction (they are copied, so nothing opens the originals).
What remains unverified until CI runs it
This session has no build, so the launch path is unproven. Verified here:
node --check on every .mjs, a green --selftest, and clean
pnpm exec oxlint. Open questions the real CI run must answer:
- Whether
TIER_MINIMAL(no top-level DLLs) bootsOrca.exeas node at all, or whether run-as-node still needsffmpeg.dll/ others — this is the core empirical result. - Whether the daemon bundle require-closure needs any other unpacked
node_modulesbeyondnode-pty(surfaces as a ready-timeout if so). - Whether any loaded module still resolves under
--app-dir(the handle probe will name it).
Recommendation for the likely-minimal tier
no-gpu is the safe minimal target to ship: run-as-node Electron does not
initialize the GPU/render stack, so libEGL / libGLESv2 / vk_swiftshader /
vulkan-1 / d3dcompiler_47 are very unlikely to load, while ffmpeg.dll and
the ICU/snapshot data are retained because the Electron bootstrap references
them regardless of run-as-node. Run --tier minimal on CI first: if it PASSES,
ship minimal; if Orca.exe fails to boot without the non-GPU DLLs, fall back to
no-gpu. full is the always-works upper bound for comparison.