A worktree is not resolvable for a beat after createWorktree resolves, so a
probe fired immediately after creation fails the RPC with selector_not_found
instead of answering. The catch collapsed that into `supported = false`, so the
composer refused and quietly built a terminal session — the gate never said no,
it was never asked successfully. Elapsed time was the only input that decided
whether a Claude launch went structured.
"Could not answer" and "answered no" are different states and only the second
is a verdict. Retry while the host cannot yet resolve the selector, with a
bounded backoff that covers the measured window with margin, and keep refusing
on the first ask for everything else. Fail-closed is unchanged: a probe that
still cannot be answered when the budget is spent refuses.
The retry is narrowed with the shared error-code matcher, which classifies a
token that transports re-wrap into a longer message without matching prose that
merely mentions it.
Codex never probes, so this race has never been able to refuse a Codex launch —
the race itself is identical for it. Recorded at the early return, because
whoever gives Codex a probe inherits the bug.
Registered-but-deselected Claude accounts were refused, which is behaviourally
identical to having no accounts at all: the auth policy does not strip, ambient
auth is the truth, and the UI names no host identity. A user who deselected
their accounts silently got legacy chat with nothing explaining why.
Nothing selected for the host runtime is two states the settings cannot tell
apart after the fact, because pruneInvalidClaudeRuntimeSelection empties the
host slot and persists null in the second one:
honest deselection -> ambient auth, UI names nothing -> SUPPORTED
the WSL-only steady state -> ambient auth, UI names the WSL account -> REFUSED
The presence of any WSL-bound account in the list decides. Simplifying this to
"none active -> supported" re-opens the auth-identity misrepresentation, so the
tests fail loudly on exactly that: five of them, across the unit rule and the
createSupport path.
An empty claudeManagedAccounts array and a missing one are the same answer:
this user has no managed Claude accounts, so nothing claims an identity and
ambient auth is the truth. Refusing on absence strands any profile that simply
never wrote the key, and it disagrees with the auth policy, whose own predicate
takes `(accounts ?? [])` for exactly this reason.
Only settings that cannot be READ stay unknown, and those still refuse — as do
a WSL-bound active account and a selection naming an account the list does not
explain.
The earlier reasoning treated a missing field as settings we failed to parse.
That conflated "not present" with "not readable"; only the second is unknown.
A descendant first observed by a capture inside its own birth second could never
be SIGKILLed: `ps lstart` is second-resolution, so that capture cannot rule out
a pid recycled later in the same second, and the merge pinned each retained row
to the boundary of the walk that first saw it. SIGTERM-resistant children forked
in that window were signalled and then never escalated -- they survived close,
quit and restart, reparented to init, and had to be killed by hand.
Advancing that boundary on any later capture would be unsound: a later capture
matching pid, pgid and start-second is exactly what an impostor would also show.
But a capture is not a match -- it is a fresh ppid walk from a root Node pins
through its own handle, so a row it re-derives is proved ours at that instant
without appealing to its start time. Chain the fence from there instead, and
take that walk at the close boundary while the root certainly still lives: the
root may leave inside the grace window, and the post-timeout refresh never runs.
A row absent from the later walk still keeps its earlier boundary, and a row no
walk has ever re-derived in a later second is still never escalated.
Claude Code 2.1.258 emits a `command_lifecycle` frame for every uuid-stamped
command it starts, completes or cancels. The frame carries a command uuid and a
state and no content, and the CLI keeps it out of its own transcript -- but it
is absent from the SDK's SDKMessage union and so from Orca's frame catalogue,
where an uncatalogued kind defaults to a substantive row. Every structured turn
therefore painted raw JSON rows into the user-visible transcript.
Catalogue it and disposition it as status chrome. The unknown-kind default stays
`timeline-substantive`: a kind we have never seen is likelier to carry content
than to be chrome, and a visible row we can catalogue later beats content we
silently dropped. A lifecycle state that reads as a failure still surfaces,
because the payload error check in `classifyProviderFrame` outranks the
catalogue.
An empty claudeManagedAccounts array is a real answer: the user has no managed
accounts, nothing claims an identity, and the ambient path is legitimate. A
readable settings object with no such field is settings we failed to parse —
the same unknown as unreadable — so it refuses.
The two are one character apart in the code and the difference is invisible
without the reasoning, so record it at the branch and pin both sides. The test
fails under the obvious "consistency fix" of treating a missing field as empty.
A hardcoded stripAuthEnv asserts a gate/policy pairing production cannot
produce, and false additionally lets launch.env inherit the runner's real
process.env. Derive via claudeStructuredAuthPolicyForSettings instead: the
gate settings type is the same Pick the policy takes, and both resolve the
account through getSelectedClaudeAccountIdForTarget.
Both call sites of the managed-account gate sat in files whose first line is
`// @ts-nocheck`, so neither was typechecked: three arguments to a one-argument
function plus an undeclared identifier compiled clean. New auth-identity
decision logic had no compiler behind it.
Move the verdict into a checked module that takes the two facts the runtime
owns — the adapter's answer and a settings getter — and decides. The runtime
class now only forwards. Move the gate reader's construction into the checked
installer too, so the nocheck file passes a plain settings closure and never
names a gate symbol.
Every reference to the gate predicate and its reader now lives in a checked
file, so the ablation that used to pass silently is a compile error at both the
create-support and reacquire sites.
Removing the file-level @ts-nocheck is a separate, larger job and is not
attempted here.
createSupport gates the create path, but a session's account state can change
while it lives. A reacquire after an unexpected child exit re-resolves the
launch and re-derives auth, with nothing re-checking the gate — so a session
created while supported could come back up in the refused shape. With the strip
predicate keyed on there being an active non-WSL account, the WSL-only user's
normalized steady state (accounts exist, none active) does not strip, and that
reacquire reaches the child with ambient auth while the UI names the account.
Gate at resolveLaunch, the one choke point every acquisition passes through,
refusing with the pre-spawn error the caller already handles. Same predicate as
create-time, now sharing one settings reader so the two cannot drift.
Claude only; Codex resolves its account on a different path and is untouched.
The runtime class that wires this does not typecheck its own `this` calls — a
missing hookup compiles clean — so the wiring is pinned behaviourally rather
than trusted to the compiler.
N-1: my presence-based conflict predicate refused a terminal launch that works
today. 'ANTHROPIC_API_KEY=' is how a user blanks a variable and the settings
pipeline preserves that empty value; an empty override cannot beat the pinned
account and the strip removes the name anyway. Back to truthiness for the value,
keeping the win32 case folding.
N-2: enter the live-auth gate only after the exit/close handlers that release it,
so no throw in between can leave an entry nothing reconciles.
N-4: the Claude transcript resolver searches config-dir-then-default and de-dupes,
matching the Codex sibling in the same file, so adopting CLAUDE_CONFIG_DIR no
longer hides history written before it.
The gate resolved the active account from the account-service snapshot's
runtime map; the auth policy resolves it with
getSelectedClaudeAccountIdForTarget(settings, { runtime: 'host' }). Those are
two sources and two resolution rules, and they disagree on a legacy settings
blob that carries the selection only in the flat activeClaudeManagedAccountId:
the accessor falls through to it, a direct read of the runtime map does not. The
gate would then refuse a launch the policy would have run under host-1 — and in
the mirror case a session could be admitted under a policy computed from a
different account than the gate approved.
Read the same settings through the same accessor so agreement is structural
rather than coincidental, and drop the controller accessor that existed only to
reach the snapshot.
No behaviour change for any state both already agreed on; Codex is untouched.
Structured Claude launches against the ambient Claude config, which the account
service keeps in sync with the selected HOST account. A WSL-bound managed
account lives inside the distro and is never synced there, so on Windows a
structured session would authenticate as whatever the ambient identity happens
to be while the UI names the WSL account — the user is told one identity and
given another.
That was unreachable only because nothing offered structured Claude on win32.
Enabling it makes it reachable, so gate it here rather than patching the auth
layer: refuse the structured path when the active managed Claude account is
WSL-bound, and let the terminal-backed path — which resolves the account per
runtime — handle that account shape.
The answer rides the agentSession.createSupport seam the renderer already
consumes, so no new capability and no renderer knowledge of account internals.
A create the host declines becomes the definitive refusal the launch fallback
already turns into a legacy native chat tab, with no error toast.
Unknown answers refuse. An install with no managed accounts claims no identity
and is fine, but an active selection that cannot be resolved — or account state
that cannot be read at all — is not evidence that the ambient identity is right.
Claude only. Codex resolves its account through a different path and its
createSupport answer is untouched, as is every Codex routing decision.
P2-1: a switch beginning inside the acquire teardown left a dead chat and no
replacement. Past that point the launch waits the swap out and refuses only if it
never settles; the entry guard still refuses outright, because nothing is torn down
there yet.
P2-2: structured children now hold the same OAuth-refresh gate a Claude PTY does,
so a managed refresh cannot rotate the token out from under a live turn.
P3: the refusal now matches the strip it guards (case-folded on win32, presence not
truthiness), and the dead structured-to-TUI builder states its auth policy instead
of silently signing a system-auth user out.
session-file-resolver's default ignored the variable the pinned account home
follows, so a CLAUDE_CONFIG_DIR launch wrote one tree and mobile read another. The
Task-4 test now resolves with no root override (mobile's own call) and checks the
answer against the root the CLI itself reports, instead of mirroring the code under
test's own expression.
The optional dep plus a {stripAuthEnv:false} fallback meant a dropped wiring
under-stripped silently. Required at all three hops, asserted at install time for
the @ts-nocheck caller, and the settings-to-policy mapping is now a named tested
function.
The main process has had a complete, correctly gated Claude Agent SDK lane for
a while, but no renderer ever asked for it: the launch route accepted only
`codex`, and the create path was typed `agent: 'codex'` end to end.
Widen both to the structured provider union that already exists
(`AgentSessionHandleProvider`), and generalize the codex-named create path
instead of adding a Claude twin beside it. The pending-launch registry is now
keyed by agent as well as workspace — a shared key handed a second caller the
first agent's intent, so a Claude and a Codex launch in one worktree collided.
Windows, per agent. Codex's client-side win32 refusal is deliberate and settled
elsewhere, so it stays exactly as it was. Claude's answer is no longer guessed
from the client's platform: a structured session fences its provider child on
that child's process start time, and only the executing host knows whether it
can read one. `agentSession.createSupport` already answers precisely that, per
agent, and had no renderer caller — so the Claude create path asks it before
creating and turns a "no", or a probe it cannot get answered, into the
definitive refusal the launch fallback already handles. Fail closed either way.
That refusal mapping also closes a real gap: the host reports an unsupported
location by throwing `structured_agent_session_unsupported`, which reaches the
client as a transport rejection rather than a refusal envelope, so
`StructuredAgentSessionCreateRefusalError` never fired. The launch would retry
the create, strand itself in `visibilityUnknown`, run no legacy fallback, and
show an error toast.
Close a fail-open hole while Claude and win32 become reachable: `create` with a
client-supplied location, and `ensure`, both skip the worktree-resolving support
check. They now ask the executing host the same question directly, so a host
that cannot fence a provider child no longer creates one on a client's say-so.
Also deletes `structured-agent-session-provider-routing.ts`, a duplicate of
`structured-agent-session-provider-support.ts` with no importers.
WSL, SSH and paired hosts, floating workspaces, draft prompt delivery, explicit
TUI customization and initial session options all keep refusing; folder
workspaces keep working.
The SDK path stripped ambient Anthropic auth unconditionally, let an explicit
agentDefaultEnv override beat a pinned managed account, and had no account-switch
guard. Reuse the terminal preflight's own predicate and messages so both transports
strip, refuse, and report identically, and cover the CLI transcript location that
mobile native chat depends on.
The direct root kill goes through the handle Node owns, not through a pid:
libuv drops that handle in the same turn it reaps, so the signal either
reaches the process Orca spawned or reaches nothing at all. Gating it on a
process-table probe therefore bought no safety and cost the tree its only
fallback whenever the probe declined -- a first capture landing in the fork's
own second, a recycled descendant pid voiding the snapshot, or a process table
that could not be read on either platform.
Identity verification stays where a bare pid is genuinely addressed: Windows
`taskkill /T /F`, and the descendant sweep's own revalidation before it signals.
Also stops a declined root probe from collapsing an observed `live` or `exited`
descendant verdict into `unverifiable`, and stops a successful taskkill from
reporting `unverifiable` because a later probe found the root correctly dead.
Replace the translucent bg-muted/25 with an opaque color-mix blend
(40% muted on background) to ensure scrolled rows don't show through
the sticky header. Add test coverage for header styling and layout.
The relay root does not declare it (it belongs to the private apps root), and
Terraform rejects an undeclared -var, so the first public Deploy Relay Staging
run failed at the C4 image bind.
setup-node's cache: pnpm runs 'pnpm store path' from the repository root,
where packageManager pins pnpm 12; the shim it downloads fails to execute on
the runner, so the step dies before auth. Cloud Verify never used the cache
and passes; the six relay workflows that copied it from orca-cloud (root
pnpm 10 there) now match.
Phase 6 of the relay split: the relay's deploy/operate surface moves under cloud/ with 24 cloud-* workflows gated on ORCA_CLOUD_OPERATIONS_ENABLED, the Cloud SQL rollout lease action, the relay Terraform root (dual-accept identities for both repositories), scripts, docs, CODEOWNERS, and a terraform validate job in Cloud Verify.
* perf(hot-paths): delete allocation-only work in sort, explorer, monaco, rpc, snapshots
* fix(perf): revert snapshot revision fast-path — same revision can carry a new session
* perf(hot-paths): drop the unproven rpc buffer rewrite, dedupe the equality helpers
- Revert the unix-socket chunk-carry change. Its comment claimed it avoided
O(n^2) rescans, but chunks is reset to [remainder] every data event, so the
join plus the tail byteLength is two passes where the old code did one;
benchmarks showed no win. It also moved consumed-frame bookkeeping out of the
closure, so a synchronous throw from the handler would re-dispatch frames.
- project-host-compatibility: fold the two byte-identical array comparators
into one generic arraysEqualByJson.
- smart-attention: drop the leftover byTab.size === 0 branch that returned the
same value as the line after it.
* perf(renderer): stop five timers from ticking behind a hidden window
* perf(renderer): park hibernation and panel-watchdog work behind a hidden window
Narrowed from five timers to two, and made both correct:
- Gate on getWindowParkVisible(), not raw document.visibilityState. macOS can
wedge visibilityState at 'hidden' with no further visibilitychange, which
would park these for the rest of the session.
- Add a real becoming-visible pass via subscribeWindowParkVisibility, so resume
does not wait out the remaining interval. Unsubscribed on stop.
Dropped the other three gates:
- terminal-delivery-watchdog: it is the recovery lane for the byte-drop bug the
stale-visibility latch exists for; parking it costs a frozen terminal.
- crash-diagnostics: the dashboard-popout surface is normally occluded, so it
would sample once at startup and never again.
- use-contextual-tour: attempts only increments past the gate, so a hidden
window turned a self-clearing 20-attempt interval into a permanent one.
Tests stub visibilityState and the stale latch; both watchdog cases are RED
against the previous raw-visibilityState implementation.
* perf(renderer): narrow vault and editor subscriptions off the every-write path
* fix(perf): revert EditorPanel narrowing — downstream hooks need the full openFiles list
* perf(renderer): make the vault session-id cache resettable between tests
Every production writer replaces agentStatusByPaneKey, but test fixtures
commonly mutate it in place, which would keep serving the key cached for that
identity. Fold the WeakMap into the existing reset hook.
* fix(windows): refuse tree-kills of Orca's own Chromium pids and record the rest
G2 is 20 field reports that share only a symptom. It is at least four
fingerprints: ~15 Windows `reason=killed exitCode=1`, 3 POSIX SIGKILL under
memory pressure (G4-oom), 2 duplicate reports of one macOS V8 Proxy Resolver
SIGKILL, and 1 `0x80000003` install-dir ACL crash (G1; #17740 ships in
v1.4.196 only, not 1.4.195). Nothing here claims to fix all of them.
Two changes:
1. Behaviour. `classifyWindowsTreeKillTarget` returns `own` for any direct
child of the main process — which our renderer, GPU and network-service
utility all are — so PTY teardown could `taskkill /T /F` Orca's own UI
(#10680). Both that classifier and `terminateWindowsProcessTree` now refuse
any pid Electron is currently accounting for in `getAppMetrics()`.
2. Diagnosis. An Orca-issued kill and an external one are byte-identical in
every field the crash report records today, so the cluster is undecidable.
Every main-process force-kill choke point now records a durable
`self_tree_kill` breadcrumb, and `process_gone` reports carry
`selfInitiatedTreeKills` naming the pid and its offset from the death.
A refused kill records `self_tree_kill_refused_own_chromium`, which is
falsifiable: if it ever shows up in the field, we were the killer.
* fix(crash-reporting): coalesce self-kill breadcrumbs and scope the discriminator
Round-1 review remediation. Three blocking findings, all accepted.
1. Breadcrumb flood (accepted). recordSelfInitiatedTreeKill wrote an
uncoalesced durable crumb from two routine teardown paths, and the
reviewer reproduced 12 terminal closes x 3 process groups completely
evicting the 30-slot ring — including this PR's own refusal crumb — plus a
forced writeSync per killed group. It now uses the existing
recordCoalescedDurableCrashBreadcrumb (5s window for pid-addressed
taskkills, 60s for routine group/job teardown), so a burst costs one ring
slot and one flush. The refusal crumb is coalesced per victim pid, so a
retry loop cannot flood while a distinct pid always gets its own crumb.
Regression test replays the reviewer's exact 12x3 reproduction and asserts
the refusal crumb and a pre-existing gpu_process_crashed both survive.
2. Undifferentiated count (accepted). posix-process-group and win-pty-job are
structurally incapable of reaching a Chromium process, and scope was absent
from the persisted string. Scope is now in every entry
(`<scope>/<site>/pid<N> +Nms`), and the count is split:
selfInitiatedTreeKillCount now counts only pid-addressed taskkills — the
kills that can land on a recycled pid that is now our renderer — with
pty-scoped sweeps in selfInitiatedGroupKillCount. The list is renamed
selfInitiatedKills because it carries both, and sorts pid-addressed kills
first so truncation never drops the discriminating ones for teardown noise.
The reviewer's repro (routine macOS terminal close + unrelated exit-133
crash) now yields selfInitiatedTreeKillCount undefined.
3. Recording gaps and a false comment (accepted). New
admitSelfInitiatedTreeKill gate: it refuses own-Chromium pids and records
the rest, and all three main-process taskkill families now go through it —
terminateWindowsProcessTree plus codex-accounts/service.ts and
claude-accounts (which keep their own spawn lifetimes). The false "single
taskkill choke point" comment is gone. The runProcess choke point the
investigation asked for is instrumented via a
setProcessTreeKillObserver seam in src/shared/child-process — shared code
runs in the CLI and relay so it cannot import the main breadcrumb store —
registered in main preflight. The codex app-server POSIX group teardowns
and the claude POSIX branch record too. The module doc no longer claims
absence is discriminating: it enumerates what is instrumented and names the
direct process.kill(-pid) sites that are not.
Non-blocking, also fixed:
- Breadcrumb calls moved out of the try blocks whose catch is the ESRCH
contract (posix-pty-process-groups, codex teardown, claude POSIX), so a
throw from the diagnostic path can never be reported as a failed kill.
- Detail truncation now bounds the first entry too, matching its comment.
- own-chromium-tree-kill-refusal.test.ts renamed to
own-chromium-tree-kill-guard.test.ts, colocated with the module it tests.
Not changed, with reasons:
- Date.now() vs performance.now(): kept. Offsets are computed against
goneAt = Date.now() in process-gone-recorder; a monotonic clock here would
make the offsets meaningless. The reviewer verified this and agreed it is
not a defect.
- app.getAppMetrics() per force-kill remains unbenchmarked. It reads
in-process browser state rather than enumerating the OS process table, and a
TTL cache would let a recycled pid slip past the refusal, so it stays
uncached.
- The ~15 remaining direct process.kill(-pid) sites (browser routes,
notebooks, automation prechecks, ephemeral VM recipes) are not instrumented.
Rather than claim coverage this PR does not have, the module doc names them.
claude-command-process.ts crossed the 300-line cap, so terminateClaudeProcess
moved to claude-login-process-termination.ts. No max-lines suppression added.
* fix(crash-reporting): scope the self-kill guard to its real host topology
Round-2 review findings on the own-Chromium tree-kill guard.
BLOCKING 1 — "the own-Chromium refusal is a no-op in the process that issues
the pty-descendant-sweep taskkill". Correct on the mechanism, wrong on the
consequence; REBUTTED in part and documented in full.
Confirmed: the only non-test `setAppEnvironment` installs are
main-process-preflight.ts:177 (Electron) and orcad-entry.ts:84 (Node, whose
`getAppMetrics()` is `[]`); daemon-init-fresh-import.ts is a test harness. So
in the standalone daemon `readOrcaChromiumProcessPids()` is empty and
`admitSelfInitiatedTreeKill` always admits.
But that is not a live hazard. `killWithDescendantSweep` reaches
`terminateWindowsProcessTree` only when `verifyWindowsTreeKillTarget` returns
`own`, and that walks ancestry back to `deps.ownerPid ?? process.pid` — the
KILLING process's pid. In the daemon that is the daemon's pid. Orca's Chromium
processes are children of Electron main, a sibling of the daemon, so their
chain never reaches it: hop 0 lands on main, and within MAX_ANCESTOR_HOPS the
walk dead-ends and returns `foreign`. The reviewer's probe passes
`ownerPid: 1000` with the renderer as a direct child of 1000 — that is the
Electron-main topology, where the AppEnvironment IS installed and the guard DOES
fire, not the daemon's. On an orcad/SSH host there is no Chromium on the box at
all, so `[]` is accurate rather than degraded.
Locked in as tests rather than prose (own-chromium-tree-kill-guard.test.ts):
a renderer classifies `foreign` from a daemon ownerPid with an empty pid set,
and `own` from main's ownerPid with an empty set — the falsifiable pair showing
the pid set is load-bearing in main and nowhere else. Documented the host
coverage in orca-chromium-process-pids.ts and own-chromium-tree-kill-guard.ts.
One genuine hole the finding exposes: `signalProcessTree`'s `taskkillTree` is a
fourth pid-addressed taskkill family (non-blocking item 2), it runs in the
daemon/relay/CLI where the guard cannot run, and it guarded only on
`!child.pid`. Reusing the predicate the codex login teardown already uses, the
win32 branch now refuses a reaped child and falls back to `killRoot` — the same
shape as the existing `!child.pid` branch. That closes the reaped-then-recycled
pid path in every host.
BLOCKING 2 — module doc overstates coverage. Rewritten: the ring is per-process
and its only reader lives in Electron main, so a count on a `render-process-gone`
covers main-issued kills only. Sites are now split into main-only, main-and-
other-hosts (runProcess choke point, POSIX PTY group sweep, Windows Job Object —
which record into a ring nothing reads when they run in the daemon or relay),
and never-instrumented, with the note that a daemon/relay omission is a
diagnostics gap, not a missed suspect, per the topology argument above.
BLOCKING 3 — the three out-of-main instrumentation sites were untested. Added
regression coverage: the runProcess seam on both branches plus the reaped-child
refusal (process-tree-termination.test.ts), the group sweep recording only
groups it actually signalled and skipping an ESRCH group
(posix-pty-process-groups.test.ts), and the Job Object recording the shell pid
only on `terminated` (windows-pty-job.test.ts). Verified red: reverting the
three production files to origin/main fails 7 of the new tests.
BLOCKING 4 — the Windows evidence validates a single-process model. Accepted.
The main2.js arms exercise `pty-descendant-sweep` inside one Electron process;
that models the in-process/degraded daemon and the local PTY provider, not the
standalone daemon. Arm C's "the 449351d6 shape is not producible with the guard"
holds for main-issued kills only. In the daemon the shape is blocked one layer
earlier, by the ancestry check, which the arms do not exercise.
NON-BLOCKING taken: `recordSelfInitiatedTreeKill` moved outside the native
`terminateJob` try in windows-pty-job.ts, so a diagnostics throw can no longer
downgrade a real termination to `unavailable` and escalate callers to a broader
kill; covered by a test. The "all three families" parenthetical is gone with the
doc rewrite. `pnpm build:relay` run: exit 0, all seven targets built.
NON-BLOCKING declined: codex-accounts/service.ts records before the spawn
because a refusal must prevent the spawn — the crumb means "we were about to
kill this pid", which is the artifact worth having; the existing comment already
says so. `app.getAppMetrics()` perf is unbenchmarked and unchanged by this round.
Verification: pnpm tc clean; oxlint clean on touched paths;
check:code-quality:changed 0 new findings; oxfmt applied. 730 tests pass across
shared/child-process, main/crash-reporting, main/pty, main/windows and the guard
and descendant-sweep suites. The 4 failures in providers/git/codex-integration
reproduce on HEAD without these changes.
* fix(crash-reporting): keep the reaped-pid skip from flipping the termination barrier
The win32 hasExited short-circuit correctly avoids taskkill on a pid Windows
may have reissued, but it resolved `true` — verified tree termination. A
taskkill against a reaped pid already resolved `false`, and run-process turns
`true` into barrierTerminationVerified + terminationReporter.report(), which
releases the git admission grant on root exit instead of on `close`. That
admits the next git command while a descendant holding the inherited pipes is
still writing the repo. Resolve `false` so the skip changes only which process
we refuse to signal, not what the barrier claims.
* perf(renderer): index diff comments, skip no-op hydration, drop duplicate normalizes
* fix(perf): keep tree-path stability hook render-pure for react-doctor
* fix(perf): publish the returned array from the tree-path stability hook
The ref was written with the raw input but read in render to pick the return
value, so it trailed one commit and a wave of content-equal arrays flipped
identity every render — re-firing the uncancellable full-tree git check-ignore
it exists to prevent. Publish `stable` instead, keyed on `[stable]`.
Also drops the hydrateOverrides no-op skip: notifyChange is not a bare wakeup
(it drives getPanesNeedingOverrideFit -> safeFit and the remote viewport
re-claim), and the branch never fires in production anyway.