Skip to content

Stage long agent launch lines where they are typed, so they arrive whole (step 2 of 7) - #23962

Merged
brennanb2025 merged 44 commits into
mainfrom
brennanb2025/launch-prompt-on-command-line
Oct 6, 2026
Merged

brennanb2025 merged 44 commits into
mainfrom
brennanb2025/launch-prompt-on-command-line

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 11 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​805 $\color{#cf222e}{\Huge{\mathbf{−}}}$​434 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​371
Prod 12 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​303 $\color{#cf222e}{\Huge{\mathbf{−}}}$​253 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​50

The problem

When Orca starts an agent, it types the agent's launch line into a shell that is often still starting.

A long or multi-line launch line breaks there: lines go missing, the shell sits at a quote> prompt, or the agent starts with half its text.

Why: macOS keeps at most 1,024 bytes of a line typed into a terminal that is still starting, and a newline inside the line is read as Enter, so bash 3.2 runs each line of a multi-line prompt as its own command and fish drops an early multi-line write.

Today Orca avoids this only by keeping long or multi-line prompts off the launch line (#24257 pastes them once the agent is ready), so any launch line that still gets long or multi-line (a resume line, an SSH background launch, a terminal created with a long command, a multi-line zsh prompt up to 8 KB) is typed raw and can be cut or run in pieces.

Before and after, for the agent

What the agent receives does not change. The agent gets the same command and the same prompt text as before; nothing is added to or removed from the prompt. What changes is how that command reaches the shell, and only when the command is long or multi-line.
Before: Orca typed the whole launch command into the terminal, often while the shell was still starting. A multi-line prompt's newlines were read as Enter (bash 3.2 ran each line as its own shell command), and a line over ~1,024 bytes typed into a still-starting macOS terminal lost the rest. So the agent started with half its prompt, or never started and the shell sat at a quote> prompt.
After: Orca writes the exact same command into a private, self-deleting script (mode 0600) and types one short line that runs it, e.g. . '/…/orca-launch-a1b2.sh'. The shell runs exactly the bytes Orca meant to type, so the agent starts with its full prompt.
Unchanged: a launch command of 512 bytes or less with no newline or control character is typed exactly as today, so most launches look and behave the same.

What changes for you

A long or multi-line launch line on a macOS or Linux host (local, SSH, background) now reaches the agent whole, in bash, zsh, sh, dash, fish, ksh and mksh.

What you see in the terminal is one short line instead of the long one: . '/…/orca-launch-<random>.sh' in bash, zsh, sh and dash, eval (string collect < '…') in fish, and /bin/sh '…' for Orca's own long agent lines in ksh and mksh. The agent runs as a normal foreground job, so Ctrl-Z and Ctrl-C behave as if the line had been typed.

If Orca cannot write the launch script (disk full, unwritable temp dir), the terminal prints [orca] Could not stage the launch command (...) and types the line as before, so you see why it may fail instead of a silent quote> prompt.

In tcsh, nu and other shells whose quoting Orca does not speak, Orca's own agent launch line runs through /bin/sh when it is long, has a control character, or holds a character those shells do not read literally inside Orca's quoting (!, backslash, ", $, backtick); a plain line is typed as before, and commands you wrote yourself are always typed exactly as before.

Lines of 512 bytes or less with no control character are typed exactly as today.

Known limit: the shell's history holds the short line, and the script deletes itself when it runs, so pressing up-arrow and Enter does not relaunch the agent.

Scope

In scope: the places Orca types a startup line into a terminal on a macOS or Linux shell through the terminal daemon, the SSH relay or the in-process terminal provider, for local, SSH and background launches.

Not in scope:

  • Windows hosts, in every shell there (cmd, PowerShell, Git Bash, WSL panes): cmd and PowerShell cannot run the script, and Git Bash and WSL panes go through the Windows host's own launch path. Step 3 routes those prompts to a launch file.
  • The SSH fallback without the relay (plain shell over SSH), which still types the line as soon as the channel opens.
  • Anything typed into an already-running terminal, and which prompts ride the launch line at all (that rule stays feat(agent-launch): keep long prompts off the launch line and paste them after readiness (step 1 of 7) #24257's until step 3 relaxes it).

The plan, and where this PR sits

This is step 2 of 7 of the agent launch plan:

  1. feat(agent-launch): keep long prompts off the launch line and paste them after readiness (step 1 of 7) #24257 (merged): the host owns prompt delivery; a prompt the command line can't safely carry is pasted only once the agent is ready, and only when the agent is proven to be in front.
  2. This PR (Stage long agent launch lines where they are typed, so they arrive whole (step 2 of 7) #23962): long launch lines arrive whole.
  3. Carry an agent's first prompt on its command line instead of pasting it (step 3 of 7) #24080: the first prompt rides the agent's command line, with a "prompt wasn't sent" notice and Copy prompt on five launch surfaces.
  4. Start orchestration workers with their brief on the launch command line (step 4 of 7) #24081: orchestration workers get their brief on the launch line.
  5. feat(agent-launch): host-assigned caller identity and a launch record written when the tab exists (unification 1 of 3) #24934: Orca assigns each caller its identity and keeps a launch record, so after a restart mid-launch a retry finds the running agent instead of "couldn't confirm".
  6. feat(agent-launch): show an agent's tab at once, where the caller asked #25430: the agent's tab appears at once, in the split you asked for.
  7. feat(agent-launch): the desktop AI buttons start their agent through agent.launch #25624: the desktop AI buttons (Fix checks, Resolve conflicts, commit/push recovery, Explain commit) go through agent.launch (supersedes feat(source-control): start the AI buttons' agents through agent.launch (step 2 of 2) #23748).

How it works

  1. When it accepts a spawn, the host that owns the terminal looks at the line it is about to type; a line over 512 bytes (half of macOS's limit) or with a control character is written to a private (0600) script whose first command deletes the script. The 512-byte limit and the control-character check are feat(agent-launch): keep long prompts off the launch line and paste them after readiness (step 1 of 7) #24257's, imported rather than copied.
  2. The host then types one short line that runs the script as the shell's own foreground job: . '<script>' in bash, zsh, sh and dash; eval (string collect < '<script>') in fish, because fish runs a sourced file without job control; and /bin/sh '<script>' for Orca's own long agent lines in ksh and mksh, because ksh runs a sourced file inside the shell's own process group. In tcsh, nu and other shells Orca's quoting does not cover, an Orca-built line also goes through /bin/sh when it holds !, backslash, ", $ or a backtick, which those shells would not read literally.
  3. The host removes a script the shell never ran when the terminal exits. Once per app start, at the first staged launch, it also sweeps leftovers older than an hour from the temp dir.
  4. SSH background launches now let the relay type the line, as SSH terminals already did, so the renderer's separate writer (which never staged) is gone.
  5. The terminal daemon protocol moves to 40 (main took 39 for fish panes in fix(terminal): give plain fish tabs Orca's codex function without changing fish's startup #24284). New terminals are never placed on a v39 or older daemon, so it is never sent a staged line; its existing sessions keep running. A respawn that reuses a session id still owned by an old daemon goes to that daemon, as before.

Why this approach

The decision is made where the bytes are written, by the host that owns the terminal, so one rule covers daemon, relay and in-process terminals instead of a check on every launch path.

A script the shell runs executes the exact bytes Orca quoted; no escaping trick survives every shell's line editor.

Alternatives considered: passing the agent line through an environment variable (Orca already does this for setup-sequenced startup, but for agent lines the value stays in the shell's environment, is inherited by every child, and needs shell-specific syntax to read) and making the agent the terminal's first process (it skips Orca's shell wrappers, setup sequencing and the "tab stays a shell" behaviour).

How this differs from the common pattern (comparison from the first architecture review of this change):

  • Same mechanism: the common pattern also stages a startup command in a script the shell runs.
  • Intended difference: Windows hosts never stage, because cmd and PowerShell cannot run a POSIX script; step 3 routes those prompts to a launch file.
  • Intended difference: in shells whose quoting Orca does not speak, only Orca's own agent lines go through /bin/sh, and only when they need it; a user's command is never rewritten.
  • Intended difference: a failed stage types the full line with a visible notice rather than refusing the launch, so bookkeeping never blocks the user's action.

How this differs from the previous version of this PR

The previous version sat on #23748 (desktop AI buttons); this version sits on #24257 and contains none of #23748's changes. The daemon protocol bump is 40 instead of 39, because main took 39.

Changed in this round's review:

Size

23 files, +1,108/−688 against #24257's branch.

Testing

  • The PR's own test files plus every changed feat(agent-launch): keep long prompts off the launch line and paste them after readiness (step 1 of 7) #24257 test file: 15 files, 219 tests pass. They include real-shell tests that launch bash, zsh, sh, dash, fish, ksh and tcsh through a pseudo-terminal and check the agent gets its argument byte for byte and runs in its own foreground process group (that check fails for every fish and ksh case without this round's fix).
  • tc:node, tc:web and tc:cli pass; oxlint and check:code-quality:changed are clean.
  • Live QA on a Mac (zsh, bash 3.2, fish 4.9, tcsh) and on Windows (cmd, PowerShell 5.1 and 7, Git Bash, WSL), main vs this PR, is in the status comment.

Visual Proof

N/A: no UI changes; this only changes whether a long launch line arrives whole. Live QA evidence is in the status comment.

Notes

Author X handle: @BrennanKB5

@brennanb2025
brennanb2025 force-pushed the brennanb2025/launch-prompt-on-command-line branch from 19e020e to 1245810 Compare September 30, 2026 08:46
@brennanb2025 brennanb2025 changed the title Start agents with their first prompt on the command line instead of typing or pasting it into the agent Stage long agent launch lines where they are typed, so they arrive whole (step 1 of 3) Sep 30, 2026
The host's agent.launch typed any launch prompt into the shell as part of the
launch command. A long or multi-line prompt then ran line by line in the
shell, and an agent that never showed readiness or crashed at startup had
nothing guarding where its text went.

agent.launch now carries a prompt on the typed line only when the line stays
one line, control-free and at most 512 bytes; otherwise the agent starts
clean and the host pastes the prompt once the agent's own ready signal fires
(bracketed paste plus its composer marker or a quiet render, read only after
the shell's last hand-off, never while the pane's own shell is proven in
front), with main's draft-paste bytes and an Enter 50 ms later. Orchestration
worker starts wait on tui-idle as before. A replay-safe launch admits and
claims its ledger row in one write, Qwen Code gets a second Enter, the
desktop and phone share one launch-refusal classifier, and hosts advertise
agent.launch.prompt-carry.v1.

Split out of #23748, which moves the desktop source-control buttons onto
this path.
…h launch line, as main did

#24257 moved every multi-line or over-512-byte prompt off the typed launch line and pasted it after readiness. The phone's AI buttons and review notes, whose multi-line prompts main typed whole into zsh, then reached Claude 0.5-3 s later and their RPC reply waited for the paste.

The host now names the shell a local macOS or Linux line is typed into, the way the spawn picks it, and a multi-line prompt rides a zsh line when every line is at most 512 bytes and the whole line at most 8 KB. A real-zsh test types such a line through Orca's own ready barrier and startup write, including when a slow user config makes the write land early. Elsewhere the measured unsafe cases keep the paste: bash 3.2 runs multi-line lines piecemeal, fish drops an early multi-line write, and any shell loses a line over 1 KB written early.
…unch-host-prompt-delivery

# Conflicts:
#	src/main/runtime/tui-idle-evidence.ts
…dows lane's gate scan

The Windows lane registration check read `const ZSH_PATH = process.platform === 'win32'` (the head of a multi-line ternary) as a Windows-true flag, so `describe.skipIf(!ZSH_PATH)` looked like a Windows-only suite. The file is POSIX-only; the zsh lookup is now a function.
… module

Main's protocol-version.ts sits at the 300-line cap, so the prompt-carry capability pushed it over. The four agent.launch capabilities and their doc move to agent-launch-runtime-capability.ts, re-exported by name and spread into RUNTIME_CAPABILITIES at the same position; the advertised lists and every export are unchanged.
…wn module

`export *` from protocol-version left the four names undefined under the mobile recording loader, which resolves a relative import through a Proxy with no own keys, so 37 phone recordings lost agent.launchReplay. Importers now name agent-launch-runtime-capability directly; protocol-version only spreads its list.
… local caller id

Both were inert in step 1 and existed only for step 2. agent.launch will become a
public plugin API, so every wire field is permanent once shipped; a top-level
viewMode reads as "choose terminal vs chat", which the host decides. Step 2
introduces placement and view intent under a placement object instead.
…unch-host-prompt-delivery

# Conflicts:
#	src/main/ipc/desktop-renderer-runtime-capabilities.test.ts
…es' hold anchor

Main (#24375) moved Codex's provisional-header check into codex.json's
provisional_startup hold anchor and deleted codex-terminal-readiness.ts, so the
launch readiness hold now asks showsHoldAnchor, as main's own settled check does.
…and census the zsh fixture

Main (#24375) answers a name-only title from each agent's rule file ahead of the
sustained-title lane, so gemini.json's name_title settled a launch readiness wait
on the shell's auto-title while Gemini was still booting. A launch now asks quiet
of every weak idle verdict, as that lane did.

Main's readiness census requires a recorder for every runtime fixture; the zsh
prompt recording is a non-agent control. Gemini's synthetic baseline is
regenerated for this PR's stated change: a bare gemini title is no longer its
rest mark, so name-only rows settle weak, and a fresh working or blocked status
is no longer overridden.
A launch line that is long or has several lines could lose lines or wedge a
shell that is still starting, because it was typed into the terminal before
the shell could read it whole. The terminal host (local daemon, and the SSH
relay) now writes such a line to a script file and types one short line that
runs it, for every POSIX shell; a shell that cannot read Orca's quoting runs
Orca's own agent lines through /bin/sh. If the script cannot be written, the
terminal says so.

The SSH background launch path no longer types the line from the renderer;
the relay stages it like any other terminal.

Bumps the terminal daemon protocol to 40 (main took 39); a v39 daemon keeps
its sessions and gets no staged line.

Restacked onto #24257 from the version reviewed on top of #23748; carries
none of #23748's changes.
@brennanb2025
brennanb2025 force-pushed the brennanb2025/launch-prompt-on-command-line branch from 1245810 to 55fd9fe Compare October 2, 2026 06:40
@brennanb2025
brennanb2025 changed the base branch from brennanb2025/desktop-action-buttons-agent-launch-2 to brennanb2025/agent-launch-host-prompt-delivery October 2, 2026 06:41
@brennanb2025 brennanb2025 changed the title Stage long agent launch lines where they are typed, so they arrive whole (step 1 of 3) Stage long agent launch lines where they are typed, so they arrive whole (step 2 of 7) Oct 2, 2026
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.
… is proven in front

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.
brennanb2025 added a commit that referenced this pull request Oct 2, 2026
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.
…unch-host-prompt-delivery

Main's #24601 added a config-driven readiness lane (composerReadyCaptures, census-checked:
zcode, opencode, opencode2) in the same module this branch adds. The lane table is now the one
source of truth for which agents wait on their composer marker alone; DSH and Grok follow main's
table (tui-idle lane), and waitForWorkerAgentReady routes a fresh worker through this branch's
worker-start wait. Main's extracted startup builders now carry this branch's prompt carry:
buildRuntimeAgentTerminalStartupOptions plans through planExecutionHostStartupWithPromptCandidate
(OpenCode model prep, then the typed-line carry decision), and resolveWorktreeCreateAgentStartup
threads onStartupPromptCarry.
…h's capabilities moved out of protocol-version

The mobile web bundle check failed at 124 assets against a ceiling of 123. Main already sat
exactly on that ceiling: its sweep table read 69 scripts at 16 routes while the tree builds 73,
the whole margin of 4. This branch imports the agent.launch capabilities from their own module,
so protocol-version is no longer pulled into the root layout and four other routes. That moves
which routes share which modules, and the Qoder capability module, imported by protocol-version
and the AI-vault resume path, no longer shares an importer set with anything, so it gets a chunk
of its own: 74 scripts.

The fence says to re-derive the bound rather than raise it, so the sweep is re-measured on this
head (every prefix of the sorted route list). The worst route now adds 10 scripts (session), not
9, which moves the pinned shell crossing from 32 to 30 routes; main re-measured on its own lands
on the same crossing.
…rompt-delivery' into brennanb2025/launch-prompt-on-command-line

Brings #24257's merge of main (e02adc5) and its mobile bundle sweep re-measure. The daemon
protocol keeps this branch's v40 (staged startup scripts) and adds main's fish codex-function
constant at v39.
brennanb2025 added a commit that referenced this pull request Oct 5, 2026
Brings #24257's merge of main (e02adc5) through #23962. Main's refactors met this branch's
carry rule in the startup builders, so the rule now runs inside them:
- Terminal create and agent-session create plan through planExecutionHostLaunchPrompt: main's
  OpenCode one-time model preparation, then planLaunchPrompt, with the model's launch config on
  whichever plan launches. A draft keeps main's refusal of a model preference.
- The flag-prompt branch uses main's extracted builder (OpenCode run's positional message and
  prompt env) with this branch's launch-file directory grant; followupPrompt is gone there too.
- Worktree create keeps main's resolveWorktreeCreateAgentStartup, now awaiting the async builder.
- The relay strips OpenCode's startup-prompt env and probes its plugin before writing the launch
  file; the local spawn creates jcode's runtime dir before it.
- Daemon protocol v41 (launch file) keeps main's fish codex-function constant at v39.
- The new-tab launcher keeps main's structured-chat-first route with this branch's launch host.
- Main's new prompted-plan tests (Qoder, Qoder CN, DSH, OpenCode startup prompt and model)
  plan through planLaunchPrompt; this branch's new launcher tests carry main's requestId.
- The agent-session startup plan, and the work-item prompt delivery, moved into their own
  functions so the merged files stay under the line cap.
brennanb2025 added a commit that referenced this pull request Oct 5, 2026
Brings #24257's merge of main (e02adc5) through #23962 and #24080.
- Worker start: a structured session or a brief that rode the launch line still waits only on
  the setup gate; any other worker waits through main's waitForWorkerAgentReady, which routes a
  fresh launch through #24257's worker-start wait and a reused terminal to plain tui-idle.
- startupPromptPaste is threaded through main's resolveWorktreeCreateAgentStartup and its
  extracted terminal startup builder.
- Main's worker agent preflight (configured agent params, the OpenCode model probe) moved into
  local-worker-agent-launch.ts so local-worker-start stays under the line cap.
…unch-host-prompt-delivery

Brings main to 40f9e7a, which fixes main's own locale tests; no conflicts.
brennanb2025 added a commit that referenced this pull request Oct 5, 2026
…nd Grok's start answers on its composer

A paired-server worker start whose agent exited at startup typed its brief into the server's
shell, which ran it: the idle wait can settle on a shell back at its prompt, and the brief was
written with no foreground read. Both worker-start paths now check before each brief write, as a
launch prompt is checked: on a host that can find the agent in front it must be there; on one that
cannot (Windows) a shell proven in front still refuses, and anything else writes as before.

A Grok worker start waited ~10 s more than main: its only rest signal is its bare name, which a
launch holds to quiet output, and Grok animates its logo for ten seconds after its composer glyph.
A worker start for an agent whose rest signal is its bare name and whose composer draws a marker
(Grok, DSH, mimo-code) now also answers on that marker, whichever comes first.
…nto brennanb2025/launch-prompt-on-command-line
brennanb2025 added a commit that referenced this pull request Oct 5, 2026
…nto brennanb2025/launch-prompt-argv-carry

The foreground read keeps this branch's four-way verdict, on the shared launched-agent host the
worker brief check also reads.
…unch-host-prompt-delivery

Brings main to e2da3a1, which fixes main's own localization coverage audit; no conflicts.
brennanb2025 added a commit that referenced this pull request Oct 5, 2026
@brennanb2025
brennanb2025 changed the base branch from brennanb2025/agent-launch-host-prompt-delivery to main October 5, 2026 18:28
…rompt-on-command-line

Main now carries #24257 (squashed as 761d63a) and #25579, so this PR's diff is its own changes
again. #24257's files are identical on both sides; no conflicts.
@coderabbitai

coderabbitai Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 53db116a-a09d-4510-a146-fb13465228f4
📥 Commits

Reviewing files that changed from the base of the PR and between 853545d and 031bcc4.

📒 Files selected for processing (3)
  • src/main/daemon/daemon-pty-startup-delivery.test.ts
  • src/main/daemon/terminal-host-startup-staging.test.ts
  • src/main/providers/local-pty-provider-shell-readiness.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 3 remain after this review.


📝 Walkthrough

Walkthrough

The change adds shared startup-command staging and uses it in daemon, local PTY, and relay delivery paths. Staged scripts are removed during cleanup, and staging failures produce terminal notices while the original command is delivered. SSH background launches now request provider command delivery and shell-ready startup delivery instead of using renderer-side marker and fallback handling. The daemon protocol and local-build compatibility contract advance to version 41.

Priority: ➖ Normal

Merge Risk: 🟡 Moderate · up to 031bc

An SSH launch using fish may lose its startup command if the shell is slow to become ready. Confirm delivery or add a readiness gate before merging.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 031bc

Launch delivery now stores eligible commands in private temporary files. Restrictive permissions limit exposure, but a partially failed write can leave command text behind without normal cleanup. Remote startup timing and mixed-version operation remain only partly established.

Retained concerns

  • Low · security · inferred: If an exclusive script write creates the file and then fails after writing command bytes, the failure result drops the script path without removing the file. Session cleanup consequently cannot find it, and the one-time sweep skips fresh leftovers. This introduces potentially prolonged retention of sensitive launch text. Mode 0600 limits access to the owning account and privileged readers; no cross-user disclosure was demonstrated.
Security review details

Security Blast Radius

  • inferred — The identified retention concern applies to eligible launches across all three staging callers, on the local or selected SSH execution host. Restrictive file permissions bound ordinary filesystem access to the owning account; same-account processes and privileged readers remain relevant. The inspected change does not establish a new cross-tenant or privilege-escalation path.

Security Findings and Attack Paths

  • inferred — A failure after file creation and partial payload writing can strand launch text: the catch branch returns no cleanup identity, discard ignores results without a script path, and the initial sweep excludes fresh files. Disclosure would additionally require filesystem access permitted by the owning account's controls; no actual disclosure or fault-injection result was supplied.

Trust Boundaries and Controls

  • observed — Exclusive creation resists pre-existing filename substitution, mode 0600 restricts script access, and portable quoting treats script paths as shell arguments. Staging transports an already-authorized command; it does not sandbox or validate that command's execution authority.

Resilience and Maintainability Implications

  • observed — Successful staging retains a cleanup path. Daemon and local exits discard it, relay disposal discards it, and repeated removal tolerates an already-unlinked file. Daemon cancellation checked immediately after spawn precedes staging, avoiding a staged file on that cancellation branch. These controls do not cover a failed write whose result lacks the path.

Hardening Proposals

  • proposed — Retain ownership of a newly created script through write completion and remove it on subsequent write failure. Distinguish creation failure from failure after creation so cleanup never removes a pre-existing collision target. Validate this with partial-write fault injection and explicitly define recovery retention bounds.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.05% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 21 functions across 20 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: staging long agent launch lines so they reach the shell intact. The step number adds context without obscuring the purpose.
Description check ✅ Passed The description gives detailed context on the problem, behavior, scope, implementation, alternatives, and testing. However, it says the protocol changes to version 40, while the changeset updates it t…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

brennanb2025 added a commit that referenced this pull request Oct 5, 2026
…rompt-on-command-line

Main landed its own daemon protocol v40 (#25291), so staging moves to v41 with 40 attachable (the
version file, the local build contract and the pinned tests). Main's #23672 submits startup commands
with CR and drops the submit option: the daemon and relay keep the staged line under the new
signature, and the staging tests expect the sourcing line to end in CR. The renderer SSH startup
delivery file stays deleted; main's only change there removed the submit option.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

The origin/main merge left several of this PR's own staging tests asserting the old LF terminator, so seven tests are red. Main's #23672 made buildStartupCommandSubmission submit with CR; the merge updated the pre-existing tests but not this PR's newly-added ones in terminal-host-startup-staging.test.ts, local-pty-provider-shell-readiness.test.ts, and daemon-pty-startup-delivery.test.ts. I reproduced all seven failures locally.

Reviewed changes

  • Merged origin/main (853545d6d8): main took daemon protocol v40 (#25291), so this PR's staging moves to v41 with v40 attachable (version file, local-build contract JSON + TS, pinned tests).
  • Adopted main's CR startup-command submission (#23672): dropped the submit option from the daemon and relay buildStartupCommandSubmission calls; the relay now types managed.stagedStartupCommand?.command ?? startup.command.
  • Updated the pre-existing staging assertions to CR (terminal-host.test.ts, pty-handler-startup-command-*.test.ts, the older cases of daemon-pty-startup-delivery.test.ts, startup-command-staging.node-pty.test.ts).

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏


it('types a short launch line and reports it typed', async () => {
const { sub, result } = await create(`claude 'fix it'`)
expect(sub.write).toHaveBeenCalledWith(`claude 'fix it'\n`)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Main's #23672 now submits startup commands with \r, but this PR-added file still asserts ; sub.write receives claude 'fix it'\r, so this test and the expectations at lines 72, 79 and 116 all fail. Update the four expectations to \r (and the regex at line 79 to /^\. '.*orca-launch-[0-9a-f]+\.sh'\r$/).

)
expect(staged?.[1]).toContain(`\n${command}\n`)
await vi.advanceTimersByTimeAsync(200)
expect(mockProc.write).toHaveBeenCalledWith(`. '${staged?.[0]}'\n`)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same stale LF terminator: after #23672 mockProc.write gets . '<script>'\r, so this and line 263 fail. Change both to \r.

await waitFor(() => vi.mocked(lastSubprocess.write).mock.calls.length > 0)
const [script] = readdirSync(stagingDir)
expect(lastSubprocess.write).toHaveBeenCalledExactlyOnceWith(
`. '${join(stagingDir, script)}'\n`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new assertion expects , but the merged buildStartupCommandSubmission emits \r, so the write is . '<script>'\r and the test fails. Change to \r (matching the CR you already applied to this file's earlier expectations).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

🟡 Minor · Preserve the no-output grace for SSH startup. · launch-agent-background-session.ts:180-185

src/renderer/src/lib/launch-agent-background-session.ts:180-185
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Preserve the no-output grace for SSH startup.

When the relay supports shell-ready markers but the shell stays silent and the prompt probe does not identify readiness, it submits the command after 1.5 seconds. The previous path allowed 15 seconds for a silent shell because startup can still be running. Sending bracketed paste before readline is ready can cause the background agent launch to fail. Keep the 15-second grace while the shell is silent, then shorten it after output arrives.


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: bd7a169f-d2c9-45b0-9188-0cb332d33a0c
📥 Commits

Reviewing files that changed from the base of the PR and between 83de487 and 853545d.

📒 Files selected for processing (12)
  • src/main/daemon/daemon-foreground-confirmation-protocol.test.ts
  • src/main/daemon/daemon-protocol-version.test.ts
  • src/main/daemon/daemon-protocol-version.ts
  • src/main/daemon/daemon-pty-startup-delivery.test.ts
  • src/main/daemon/terminal-host-session-create.ts
  • src/main/daemon/terminal-host.test.ts
  • src/relay/pty-handler-startup-command-staging.test.ts
  • src/relay/pty-handler.ts
  • src/renderer/src/lib/ssh-background-startup-delivery.ts
  • src/shared/local-build-compatibility-contract.json
  • src/shared/local-build-compatibility-contract.ts
  • src/shared/startup-command-staging.node-pty.test.ts
💤 Files with no reviewable changes (1)
  • src/renderer/src/lib/ssh-background-startup-delivery.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 4 remain after this review.

Comment on lines +208 to +212
staging = stageStartupCommand({
command: opts.command,
shellPath: subprocess.shellPath,
orcaBuiltLine: opts.launchAgent !== undefined
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Stage before new Session(...), or discard the script when the session has already exited.

The exit closure at Line 167 reads staging before the assignment at Line 208 runs. If the shell exits before the listener is attached, the pre-listener exit replays synchronously and discards undefined. The code then writes the staged script, and session.write does nothing. No later step deletes the script, which contains the prompt.

After staging, check session.isAlive. If the session has exited, call discardStagedStartupCommand(staging).

@brennanb2025
brennanb2025 merged commit 7f25413 into main Oct 6, 2026
66 of 67 checks passed
brennanb2025 added a commit that referenced this pull request Oct 6, 2026
…ch-prompt-argv-carry

Conflicts resolved against #23962's head this branch had merged (83de487), so each keeps
main's squashed #23962 plus this branch's change:
- daemon protocol v42 (launch files), v41 (#23962's staged startup lines) now in PREVIOUS;
  build compatibility contract and pinned version tests follow.
- agent-draft-readiness keeps main's OpenCode agent-row grace (#25101) and this branch's
  bracketed-paste-off revocation of the quiet window.
- startup-line-prompt-carry.test.ts stays deleted; its rule lives in launch-prompt-carry.

Semantic follow-ups from #23672 (CR submission, one-line PowerShell quoting):
- windowsLaunchLineVerdict reads line breaks from the prompt too: PowerShell now quotes a
  multi-line prompt onto one physical line, which hid the measured multi-line rows (a 9 KB
  multi-line prompt read as exact) and the shim's line-break cut.
- Typed launch lines end in CR; raw multi-line lines submit each break with CR; the PowerShell
  multi-line expectations use the one-line backtick form.
- A duplicated pair of #23962 staging tests left by the auto-merge is removed.
- agent-paste-draft: the two identical launch-paste tails share sendLaunchPaste; tui-agent-config
  writes devin on one line like jcode (main sat at the line cap).
brennanb2025 added a commit that referenced this pull request Oct 16, 2026
… message carries (#24488)

* fix(quick-commands): save agent prompts whole, bounded by what a chat message carries

Agent quick-command prompts were silently cut to 6,000 characters on save:
the editor accepted a long paste, settings normalization trimmed it, and
reopening showed the shortened copy. The 6,000 was a Windows launch speed
threshold, not a limit on prompts.

- Storage no longer trims. A save is refused, never trimmed, when a prompt's
  chat text block is over 256 KiB of JSON (measured on its real escaped size,
  the measure the chat send schema applies) or the list is too large for one
  paired reply. Host and desktop store both check before writing.
- The editor shows the size against the limit and blocks Save past it, and
  stays open with the text when the host refuses the save.
- terminal.quick-commands.long-prompts.v1: hosts withhold prompts over 6,000
  characters from clients that do not advertise it (older clients reject a
  whole list holding one); editors keep an older host's 6,000 cap.
- The phone editor no longer drops pasted text past 6,000 via maxLength.

Launch delivery of long prompts is left to the launch-prompt series
(#24257, #23962, #24080); this branch's earlier paste-after-ready delivery
duplicated it and is removed.

* fix(quick-commands): drop a stale duplicate doc comment

* fix(quick-commands): advertise long prompts to this build's own host too

* fix(quick-commands): keep submitted editor immutable until save settles

* test(quick-commands): keep storage and save lifecycle coverage focused

* fix(quick-commands): keep portalled choices within pending form ownership

* Preserve retained credentials when settings validation refuses a save

---------

Co-authored-by: OrcaWin <alpha-eng@stably.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant