ca1c4046ed planned such a host for main's delivery, but the spawn then
refused the line it chose: a WSL spawn with no distro folder refused any
line too long to type, and the daemon refused any agent line whose staging
write failed. On a Windows host nothing proves the agent is in front, so
agent.launch and worker start keep main's typed line, and every one of
them was refused (stack QA 2a, a 5-line prompt worse than main).
The spawn now reads the same folder answer the plan read. With no WSL
folder, or this machine's temp folder unwritable, the host types the line
as is, which is main's delivery. Only a write that fails in a folder that
looked usable (the WSL probe found it, or the temp folder passed its
check) is refused. Main's planners probe the WSL folder before planning a
prompt too long to type, so the first launch into a broken folder plans
like later ones.
A refused new agent tab showed its notice twice (the empty pane's recovery
spawned it again) under a red error panel, in a tab left open. The notice
now shows once per tab, without the error panel, and a tab left with no
live terminal closes, as main's desktop route closes a refused tab.
d8cfcf397a refused an agent launch the daemon could not stage, which also
refused worker briefs (and AI-button prompts) that main pasted. The staged
script and the launch file share one folder per host, so "can write it" now
folds into takesLaunchFile, derived before planning in main: this machine's
temp folder (checked when planned, a refused write remembered for 30 s) or the
WSL distro's folder (the probe's failure, and a refused write, are remembered
for the same TTL). A host that cannot write it is planned for main's delivery
up front: the paste where main pasted, the line within the typed budget where
main typed it. Only a write that fails after the plan said yes is refused.
Stack QA on 193eeaddff:
- A WSL workspace added by its \\wsl.localhost path was planned as a Windows
launch, so the Windows shell setting decided its line: under cmd, Claude's
pointer was cmd-quoted and bash ran the backticked path, losing the prompt;
the carry also changed with the setting. A WSL path now plans as the distro
(POSIX quoting, WSL staging up to 100,000 bytes), and a pane whose Windows
shell is wsl.exe is a host fact (windowsPaneShell) the rule reads.
- A Tab or bare carriage return was measured exact on both PowerShells and is
no longer counted as damage there; cmd and Git Bash still damage it, and an
unmeasured control byte does what main does on that path.
- The WSL folder probe kept a success forever. A refused spawn now forgets it,
and the daemon refuses an agent launch whose line it could not stage
(launch_file_unavailable) instead of typing it in full.
The foreground read that gates every launch paste ran the daemon's
inspectProcess capture and then a fresh scan, each a whole-machine ps; the
fresh one also waits for any capture already running before it starts its own.
Measured here at load 5: 1.2 s a read (M4Air QA: 3.4-5.0 s, and worker starts
17.6-32 s against main's 9-12 s at load 25-84).
On a local macOS or Linux host, take the pane's root pid from the provider's
session inventory and run one ps limited to that pane's terminal. Its
foreground process group decides: the launched agent or any non-shell member
is the agent (a wrapper that did not exec its agent leads the group), a group
of shells alone is the shell. Same pane, same verdict: 2.7 ms a read. SSH hosts
keep the relay's observation and name.
Stack QA 1.2: with a launch file, the tab label and the sidebar agent row showed
"The full task is in the file ..." while the status prompt held the real text.
They read the provider session title, taken from the transcript's first
message (the pointer, cut to 96 characters). Title requests now carry their
pane, and the host that spawned it maps the title through
launchPromptShownForPane, which also recognises a pointer cut short. The pane
stays on this host: it is not sent to an SSH relay.
Stack QA 1.3: "The agent started, but your prompt wasn't sent" also showed when
the agent crashed at startup with the prompt on its command line. The receipt
now reports 'agent-exited' when the PTY exits or the shell is back in front,
and that outcome gets its own notice with Copy prompt, in every catalog.
c729128c3a typed a PowerShell prompt holding `"` or a trailing `\` on the line
because it treated 5.1 and 7 as indistinguishable, so 5.1 broke it as main
does. But this Orca chooses the executable it spawns: the requested shell, the
PowerShell implementation setting, then whether pwsh.exe is installed.
LaunchHost gains windowsPowerShell, derived by spawnedWindowsPowerShell from
those same inputs (main reads its pwsh probe cache, the renderer main's probe
over the existing IPC). On powershell.exe those prompts are measured damaged
and get a launch file (or main's paste on an AI button); on pwsh.exe they ride
the line, which carried them exactly. The PowerShell stays unknown, and main's
delivery decides, for an SSH or paired host and before the pwsh probe answers.
The Windows-lane registration scan read the const assigned from a platform
check as a Windows-only gate, though the suite runs everywhere but Windows;
find zsh in a function instead, as the real-zsh typed-line test does.
Under load the fresh foreground scan can fail to answer, which lets the
shell's prompt settle readiness (2 of 4 paired runs). The guard still refuses
that write, so assert the refused write, the property that must always hold.
Stack QA on Windows found Claude/Codex given a launch-file pointer where main's
typed line delivered the exact text: Git Bash multi-line and 8,191-character
lines, PowerShell 5-line, 20 KB and 8,191-character lines, %NAME% pairs, and on
pwsh 7 `"` and a trailing `\`. The rule treated every Windows shell like cmd.
windowsLaunchLineVerdict now judges the typed line per shell from the measured
rows: cmd (8,191-character cap, no line break), PowerShell (CreateProcess's cap;
multi-line within the typed budget; 9 KB measured damaged), Git Bash (9,089
characters measured whole, 20,551 never started). A spelled-out .cmd/.bat shim
adds its cmd hop's damage. Its verdict is exact (ride the line), damaged (main's
paste on that path, else a launch file) or uncertain (what main does on that
path: its paste, or the line it typed), so a launch file appears only where
main's line was measured to fail. PowerShell `"` and a trailing `\` are
uncertain, because 5.1 breaks them and 7 does not.
The worker brief was its only producer, and #24081 now routes the brief through
the carry rule as ordinary text, so every launch file was minted with
sensitive: false. The flag, its wire field, its parse check and the skip in the
pane prompt cache are removed; nothing that crossed the wire was released.
The 100,000-byte case failed in zsh, dash and fish under load with a truncated
argv: the harness read the capture 100 ms after the redirect created it, before
printf finished. The stub now renames the capture into place once written.
An SSH Windows host's relay may run its panes in WSL (its OpenSSH default shell,
which this client cannot see), where it writes no launch file, so a multi-line
Claude/Codex prompt there was refused where main started the agent.
describeLaunchHost now derives takesLaunchFile (false for a paired Orca and an
SSH Windows host), and the one rule gives such a host the line or the caller's
paste instead of a file.
A spawn routed to a daemon older than launch files only attaches to a session
that daemon still runs, so it is sent with neither the launch file nor its
command instead of being refused.
The age the host stamps on a process-group observation runs from the start
of its whole-machine ps, so on a loaded Mac a capture begun after the read
was asked for still read as older than 1 s and the proof was dropped. Count
an observation whose capture began after the read was asked for, less the
window a shared capture is reused across.
In tcsh and nu, every Orca-built agent line ran through `/bin/sh '<script>'`,
so the agent was a child of sh in sh's process group. When the prompt was
pasted after the agent was ready, the paste guard read sh in front and, on a
loaded Mac, reported the prompt not delivered (4/4 live runs in tcsh).
Now only a line that needs it goes through `/bin/sh`: one over 512 bytes, with
a control byte, or with a character those shells would not read literally
(`!`, backslash, `"`, `$`, backtick). A plain line such as
`claude '--dangerously-skip-permissions'` is typed as on main, so the agent is
the shell's own job.
A 20 KB Fix-checks prompt on macOS became a launch-file pointer, so the agent's
history showed "The full task is in the file ..." where main pasted the user's
text. The 16,384-character ceiling predated staging: a staged line now reaches
the agent's argv byte-exact at 100,000 UTF-8 bytes in bash, zsh, dash, sh, ksh,
tcsh and fish (measured in real shells), under Linux's 131,072-byte cap on one
argument.
The order is now the same everywhere: the line when it carries the exact text;
otherwise the paste main uses on that path ('once-agent-runs' for desktop
submit-after-ready prompts); the launch file only where neither exists.
A tcsh or nu launch line runs the agent from /bin/sh '<script>', and a
wrapper script that does not exec its agent does the same: the wrapper leads
the terminal's foreground process group and the agent is a member of it. The
fresh foreground read names the group's leader, sh, so a prompted launch was
refused or pasted late (M4Air tcsh: 2 of 4 not delivered, 2 pasted ~9 s late).
Before that read, take the host's process-group observation as positive proof
when it names the launched agent among the foreground group's members and is
younger than a ready signal's quiet window. It never proves a shell.
Main pastes an AI button's or notes send's prompt once the agent runs, so the
agent's history, /resume, the chat view and AI Vault show the user's text. A
launch file replaced nearly every such prompt on Windows with a pointer.
The caller's paste is now one of 'never', 'when-host-proves-agent' (#24257's
guarded paste) or 'once-agent-runs' (main's desktop paste for submit-after-ready
prompts). On Windows a prompt the line cannot carry exactly goes to an
'once-agent-runs' paste first; a launch file is left for the lines main would
have typed damaged or too long. Temporary, until the agent's own argv carries
multi-line text past cmd without a shim re-parsing it.
- The carried prompt's receipt is delivered only on the agent's hook turn or a fresh
foreground read that finds the launched agent in front; readiness alone never counts
and bracketed paste turned off revokes it. An unknown read is reported as unconfirmed.
- Host facts (paired, proves the agent in front) come from describeLaunchHost; a caller
that cannot paste keeps the prompt on the line as main did instead of refusing.
- Windows keeps any exact line up to cmd's 8,191 cap on the line; only damaged or
multi-line prompts ride a launch file.
- A launch-file refusal is a "failed without effects" code for agent.launch, and the
desktop closes the tab it opened for it.
- Launch-file folders carry their owner's pid; the sweep never removes a live owner's.
- The terminal-handle wait keeps waiting while that tab's spawn is still in flight.
A launch with a prompt now waits up to 60 s for the terminal agent to be
ready before it writes the prompt, and reports not-delivered when the agent
never is. The local runtime socket closes a connection idle for 30 s unless
the request is a long poll, so a launch whose agent exited at startup lost its
reply and the caller saw 'runtime closed the connection' instead of
not-delivered. Classify a prompted agent.launch and agent.launchReplay as a
long poll, as orchestration.workerStart already is for the same wait.
* test: align source-control fixtures with current store contracts
* Bound E2E package setup and retain cancelled-job traces
* Remove empty passing sentinels from opt-in socket tests
* Make SSH typing pressure fixture readiness and replies observable
* Advertise browser support for anchored terminal placement
* fix(recovery): back off a launch-failed renderer instead of tripping the crash breaker
A renderer that the OS refused to spawn (macOS exit 1003 = LAUNCH_RESULT_FAILURE; field
cause: per-user process limit, posix_spawn EAGAIN) burned the 3-reload crash-loop budget
in ~750ms and raised a "graphics driver" prompt, while the condition lasted minutes.
- launch-failed retries in place on a 250ms..60s backoff (~2 min), outside the breaker;
a loaded document resets it. Other crash reasons keep the breaker.
- Each launch failure records renderer_launch_failed_probe {spawnError} from a cheap
spawn probe, so bundles name EAGAIN/EACCES/ENOENT directly.
- The exhausted prompt says the process limit was hit (probe EAGAIN), drops the
graphics-driver wording, keeps Try Again as default, and offers no Restart:
app.relaunch also needs a free process slot and silently fails without one.
* fix(recovery): skip the launch probe on Windows and probe the prompt once
- Re-check quitting after the prompt's probe; don't re-probe on Copy Commands.
- recordRendererLaunchFailureProbe never rejects (breadcrumb write guarded).
- Windows: no spawn probe; a child per failed launch is the per-operation burst EDR scores.
* test: cover quitting and duplicate renderer launch failures
* test: use typed access in PTY delay regression fixture
* fix: scope extended launch retries to POSIX hosts
* test: cover launch probe behavior on native Windows
---------
Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
Co-authored-by: m4air <m4air@Mac.localdomain>
* ci: avoid unrelated headless server qualification
* ci: skip headless detection for ineligible draft PRs
* ci: preserve cross-host qualification and skip supplied prerequisites
* ci: include Windows server cache validation in change detection
A host that cannot prove the launched agent holds its terminal (a Windows
host, a local WSL pane on one, SSH to Windows) refuses a paste, so the one
carry rule keeps such a prompt on the line, as main typed it; Claude and
Codex still take a launch file for a long or shell-damaged prompt.
launchHostProvesAgentInFront moves to its own module, since the typed-line
shell naming beside it is gone with #24257's typed-line check.
Round-1 review of the rebuilt step 3.
- One outcome, switched on by every caller. planLaunchPrompt answers none,
on-line, launch-file or paste-after-ready; only the carried outcomes hold
the plan to launch, so no caller can read "a plan exists" as "carried".
buildAgentStartupPlan now builds only a prompt-less launch. The new agent
tab and work items had dropped a prompt an agent could not take in a file
(any agent but Claude and Codex) and reported it delivered; they now paste
it. The composer paths, background sessions, agent.launch, agentSession.create
and phone quick commands switch on the same outcome.
- One rule for hosts that type the line raw. A Windows host or a paired Orca
keeps a line to #24257's 512-byte typed budget; anything longer, multi-line
or damaged by the shell rides a launch file for Claude and Codex and is
pasted for others. A paired host is one predicate (launchHostIsPaired).
- Delivered means the agent got it. A prompt the new tab's line carried
resolves its delivery when the agent's own hook reports a turn or the
agent shows its composer, and false when the PTY never spawns (a refused
launch file) or exits first, so callers that resolve review threads or
mark notes delivered wait for that, not for the tab.
- Daemon protocol v41 writes launch files. A spawn routed to an older daemon
is refused with the launch-file reason instead of typing a pointer to a
file nobody wrote.
- A refused launch file or WSL staged line reaches agent.launch as its reason
instead of a 10 s handle timeout, and hands the prompt back with Copy prompt
for a staged line too.
- A WSL distro is probed only when the spawn needs its directory, and a
failed probe is remembered for 30 s.
- Every hook status row shows the prompt a launch file carries instead of its
pointer sentence.
A launch pasted its prompt unless a shell was proven in the terminal's
foreground, so any read that could not prove one let the prompt through. After
an agent exited at startup, its shell turned bracketed paste on at the next
prompt, readiness fired on it, and the prompt was typed into the shell:
- macOS: a pane runs its shell under login, so the process-group fence's root
was never the shell's group and never proved it; the cached foreground name
could also still name the exited process.
- Windows Git Bash and WSL: the shell-alone-in-its-job check never answers.
Now one fresh read of the terminal's foreground decides: agent, shell or
unknown. Only 'agent' lets a write through (paste, Enter, second Enter, reused
panes too); 'shell' still drops a ready signal. A Windows host never proves the
agent, so there the launch line carries the prompt at any size, as on main.
* fix(recovery): ask instead of reloading into a repeat Windows OOM with exhausted commit
When another program exhausts Windows commit (RAM + page file), the renderer
OOMs, Orca auto-reloads 250 ms later, and the new renderer OOMs again within
seconds (launch 13084: 3.5 s after the reload; launch 22912: 34 s). The crash-loop
breaker (3 in 60 s) never opens for this cadence, so the user is never told the
machine is out of memory.
Keep the first automatic reload, but when a win32 reason=oom death follows
another OOM within 5 minutes and the pre-gone host sample shows under 512 MB of
available commit, escalate to the existing recovery prompt with a new
'low-commit' cause that names the MB left and suggests closing apps or growing
the page file. Records renderer_recovery_low_commit_prompt. No-op on
macOS/Linux and when commit is healthy.
* fix(recovery): gate low-commit prompt on post-OOM readings and recovered deaths only
- Reject pre-gone samples taken at or before the previous OOM; they miss the commit that corpse released.
- Record an OOM for the repeat window only once recovery actually runs, so skipped teardown OOMs cannot suppress the next first reload.
- Skip the install-ACL diagnosis on the low-commit prompt, whose text would not explain Copy Commands.
* fix(recovery): read commit at gone time when no sampler tick followed the previous OOM
The 10 s pre-gone sampler lands between ~3.5 s repeat OOMs only ~35% of the time, so the gate usually fell back to a silent reload. A gone-time read can only over-report free commit (the corpse already released its pages), so it can miss a prompt but never raise a false one.
* fix: reject invalid low-commit readings and clarify recovery advice
---------
Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
Co-authored-by: m4air <m4air@Mac.localdomain>
* fix(linux): move Chromium shared memory off a tiny /dev/shm
Containers such as GitHub Codespaces mount a 64 MB /dev/shm by default.
When it fills, Chromium aborts the renderer (IMMEDIATE_CRASH, SIGILL/SIGTRAP)
on every reload, trapping Orca in a renderer crash loop. On Linux, stat
/dev/shm before app ready and append --disable-dev-shm-usage when it is under
512 MB or unreadable (ORCA_DEV_SHM=off|force overrides). Records a
dev_shm_policy crash breadcrumb so future container crashes are diagnosable.
* docs(linux): correct dev-shm hasSwitch rationale
---------
Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
Co-authored-by: m4air <m4air@Mac.localdomain>
* fix(markdown): cap rendered markdown size in preview tab and rich override
The Open Preview tab rendered any file synchronously through react-markdown
with no size check, and 'Open anyway' removed the rich-editor cap entirely.
A 2 MB file blocked the renderer 7.8 s at 2.3 GB (5 MB: 38 s, 4 GB), matching
scan29 crash reports where users killed a frozen Orca after opening a large .md.
- Preview tab: over 600 KB shows a 'Render anyway' gate instead of rendering.
- Both overrides stop at a 1 MiB hard cap; above it no render is offered.
* fix(markdown): gate diff-tab markdown preview by size and localize gate copy
The single-file diff Preview toggle sent the whole modified side to
MarkdownPreview with only the 6 MB large-diff limit, so a multi-MB .md diff
still froze the renderer. Wrap it in MarkdownPreviewSizeGate keyed by the
diff tab id, and add the gate's strings to all locale catalogs.
---------
Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
Co-authored-by: m4air <m4air@Mac.localdomain>
* fix(terminal): stop parking pass queueing no-op renders during pane-close bursts
Removing an active worktree with many terminal panes closed each pane in its
own commit. Every commit re-ran the parking pass, whose functional setters
queued a render even when the sets were unchanged, so each commit left work
pending and React's nested-update counter climbed past 50 (#185), taking down
the terminal workbench boundary. Dispatch only when a set actually changes.
* fix: initialize parking state mirror lazily and verify transitions
---------
Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
Co-authored-by: m4air <m4air@Mac.localdomain>