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>
A launch prompt for an agent whose CLI takes one could still be pasted into
its screen once it looked ready: the host pasted any prompt whose typed line
was over 512 bytes or held a newline (outside short-lined zsh), and the
desktop's generated-prompt launches started the agent empty and pasted. A
paste can lose text, land its Enter on a startup dialog (Codex's update
prompt), or wait seconds for the agent to look ready.
Where a launch prompt rides is now decided in one place, the startup-plan
builder (carryLaunchPrompt), for every launch path:
- on the agent's command line, which the previous step stages when the line
is too long or multi-line to type;
- in a private launch file the line points at, when the prompt is past
16,384 characters, sensitive, or one a Windows shell would damage (a
control byte; on PowerShell a ", a %NAME% pair or a trailing \; past cmd's
8,191-character cap). The execution host writes it before typing the line,
quoted for its shell, and grants Claude a read of its directory. A failed
write refuses the spawn with a Copy-prompt notice;
- pasted once the agent is ready only for an agent that takes text after
start, a prompt needing a launch file its agent is not measured reading
(only Claude and Codex are), a reused terminal, and a paired host, which is
sent a command and never a file.
This replaces the host's typed-line check (512 B, or zsh with every line
<=512 B and <=8 KiB), which existed because nothing staged a long line.
WSL writes its staged line and launch file into the distro and runs the line
in the account's own login shell (sourced in bash, zsh and sh, eval'd in fish,
through /bin/sh in ksh), with ~/.cache/orca made 0700 first.
The "prompt wasn't sent" notice with Copy prompt lives here, shared by the
new agent tab, background draft, paste timeout, work item and new workspace.
Rebuilt on the restacked staging step; the earlier version sat on a base
that carried #23748.
fish and ksh run the commands of a sourced file in the shell's own process
group, so a staged agent was not a job: Ctrl-Z was dropped (a typed line
stops), and the shell-in-front check saw the shell's group in the foreground
while the agent ran. fish now runs the script with
`eval (string collect < '<path>')`, which runs it as if typed and leaves the
shell's job-control mode alone. ksh and mksh run a long Orca-built line through
`/bin/sh '<path>'`, a real job; a long command the user wrote is typed as
before. The real-shell test now asserts the agent's process group is its own
and is the terminal's foreground group.
The staging rule also reuses hasControlByte and the 512-byte budget from
startup-line-prompt-carry so the carry and staging limits cannot drift.
* fix(native-chat): an older Orca skips and keeps a journal row of a kind it does not know
* test(native-chat): a newer build's journal row kind survives reads, writes, rewinds and reopens
* fix(native-chat): an older Orca keeps an unknown journal row kind read-only unless its writer declared it skippable
A row of a kind this build does not know, in a well-formed envelope, now latches the chat
read-only with every row kept, the same way a newer row version does. It is read past only
when its writer declared `ifUnknown` on the row: `skip` (a rewind drops it) or `carry` (a
rewind carries it after the rebuilt history, epoch, seq and fence restamped). Every existing
kind changes queue or turn state, so skipping by default would let an older build write from
a wrong fold.
- journal-row-kind-compatibility.ts: each kind states how older builds read it, typed over
every row kind, so a new kind cannot be added without a declaration.
- Rewind restates the Resume and Stop as before, then carries `carry` rows in source order;
the restatement goes back to { lifted, liveStop }.
- Replay treats a row whose body names another sequence than its stored key as malformed at
the key, so the next write never collides with it; catch-up reads stop there too.
* refactor(native-chat): drop the writer opt-in; an unknown journal row kind only latches read-only
An older Orca now treats a row of a kind it does not know exactly like a row from a newer
schema version: every row stays on disk and the chat opens read-only until an update. The
writer-declared skip/carry opt-in, its in-memory placeholder, the carry through rewinds and
the per-kind registry are removed: no current or planned kind could use them, and they can
come with the first kind that may safely be read past.
Kept: an unknown kind needs the envelope every row keeps (epoch, sequence, fence, timestamp),
else it is damage as before; a row whose body names another sequence than its stored key is
malformed at the key; the epoch row's validator names its kind. The schema header states the
rule for adding a kind: keep the envelope, and either ship the reader first or bump `v`.
* refactor(native-chat): derive the journal's known row kinds from the row union
Each kind's own-field check now lives in one table keyed by every kind JournalRow holds, and
the set of kinds this build knows is derived from that table. A kind added to the union without
a check fails to compile, rather than latching this build's own chats read-only as a newer
build's kind. A test reads one valid row of every kind.