Skip to content

Add local shell command identity markers - #16915

Open
brennanb2025 wants to merge 16 commits into
mainfrom
brennanb2025/identity-slice1a
Open

brennanb2025 wants to merge 16 commits into
mainfrom
brennanb2025/identity-slice1a

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Aug 28, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 36 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1388 $\color{#cf222e}{\Huge{\mathbf{−}}}$​97 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1291
Prod 60 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1262 $\color{#cf222e}{\Huge{\mathbf{−}}}$​184 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1078

ELI5

Orca has to work out which agent — Codex, Claude, Gemini, Pi — is running in each terminal tab, so it
can show the right icon and status. Until now its best clue for a terminal you drove yourself was the
window title, and titles are whatever a program feels like setting. If you typed codex at a plain
prompt and Orca's process scan couldn't identify the foreground program, the tab showed a bare
"Terminal 1" and no agent at all.

This adds a better clue. Orca's own shell setup now quietly tells Orca which command the shell just
started, stamped with a private tag so a program can't fake it. Orca strips those stamps out before
anything reaches your screen, and only uses them to label the tab — never to decide whether
something is alive, where your typing goes, or whether a terminal is safe to close.

User-facing before / after

Before After
You type codex yourself at a plain prompt and Orca's process scan hasn't identified it The tab fell back to the window title, which on a fresh terminal names no agent — so the tab showed no agent The shell reports the command, and the tab identifies the agent straight away
The title claims one agent and the verified command says another The title won The verified command wins
The command report can't be verified (the private stamp is missing or doesn't match) — Treated as untrusted: it is only used when the resolved title rung supplies no agent identity
Live agent hooks or an identified foreground process already name the tab's agent — Unchanged — those still outrank the command report
The private stamps themselves — Stripped before anything is stored or drawn; they never appear in your terminal. Orca emits no private markers on SSH panes and derives no command identity there.
Terminals on SSH remotes — Unchanged; this is local and daemon panes only
What the command report is allowed to decide — Display only. It never affects liveness, routing, stopping, or exit
You don't want it — Per-host-class environment kill switches leave new panes unwrapped

When it bites

People who start agents by typing them into a terminal instead of using Orca's launcher, on local and
WSL panes running bash, zsh, fish or PowerShell. The symptom was a tab that never picked up the
agent's identity.

Summary

  • emit bounded private command-start markers from Orca-owned bash, zsh, fish, and PowerShell shell integrations immediately before OSC 133 command-start lifecycle output
  • widen local and WSL shell coverage for plain panes, forward the shell feature contract through WSLENV without putting nonce values in argv, and add shell start-time/tty identity metadata
  • scan and strip markers at local and daemon PTY authorities, publish normalized display-only command facts, and use trusted command identity between process and title evidence in terminal tabs
  • keep relay/SSH paths unchanged and keep command evidence out of foreground-process, routing, stop, and exit authority

Author: @BrennanKB5

Safety

  • nonce and command text remain authority-local; renderer facts contain only normalized agent/null, trust, and command epoch
  • on PTY exit, a within-cap fragment carrying the full private prefix is dropped identically by local and daemon authorities while its raw sequence length is still charged; partial-prefix and over-cap candidates remain byte-for-byte output because they are plausibly user OSC data
  • dropping the private fragment is safer than scrubbing it: a truncated OSC row has no useful downstream meaning, and reconstruction could still expose private command material beyond the nonce
  • mismatched or unavailable nonce rows are stripped and delivered as untrusted display evidence
  • Windows 10 PowerShell omits the nonce, while PowerShell-to-cmd fallback scrubs integration environment
  • per-host-class environment kill switches conservatively leave new panes unwrapped
  • direct-context guards suppress inherited inner-multiplexer integration without breaking Orca-direct panes
  • no files under src/relay were changed and no remote transport field was added

Tests

  • full real-shell CI command: 18 files, 395 passed, 21 skipped
  • affected unit coverage: 16 files, 187 passed; prior changed-contract rerun: 9 files, 133 passed
  • shell-start reliability gate: 16 files, 258 passed; this caught SSH relay widening until marker injection was scoped back to local/daemon providers
  • final review-fix focused rerun: 5 files, 135 passed (86 scanner/local/daemon and 49 tab-agent precedence)
  • red/green contract proofs: v2-to-v3 broke both local and daemon zsh anchor assertions; removing feature widening broke seven assertions; disabling marker-only fish wrapping broke its contract; removing nonce enforcement broke marker-injection fallback
  • pnpm tc passed after a temporary TS2322/TS6133 violation proved the gate was live
  • direct changed-file oxlint, oxfmt --check, max-lines ratchet, pnpm build:relay, full pnpm build, and git diff --check passed
  • disposable headed SSH Electron run against a bundled real demo repository passed: the visible terminal executed the remote proof command, relay stayed alive, Orca emitted no private orca-cmd bytes on the SSH pane and derived no pane command identity; arbitrary marker-shaped bytes printed remotely pass through unchanged, identically to main, because this PR changes zero SSH/relay files
  • exact-worktree Electron CDP run reported brennanb2025/identity-slice1a and the expected repo root; the rendered local terminal visibly printed IDENTITY_SLICE1A_LOCAL_OK
  • exact-worktree Electron exit repro in demo-project: the rendered terminal visibly printed TRUNCATED_MARKER_EXIT_VISIBLE_OK, the captured real pty:data stream contained zero nonce-bearing chunks, and exit delivered an empty transformed frame charging all 39 stripped raw characters

Red proofs

Each implementation change below was narrowly reverted, its focused test was run red, then the implementation was restored and rerun green:

  • removed the bash missing-nonce emission guard: local-pty-shell-ready-wrapper-generation.test.ts failed 6 lifecycle cases because empty-nonce private rows painted
  • forced matching nonce rows to untrusted: shell-command-marker-scanner.test.ts failed 2 trust assertions
  • removed ORCA_SHELL_FEATURES from WSLENV: wsl-orca-env.test.ts failed the guest forwarding assertion
  • removed the trusted command-identity precedence rung: use-tab-agent.test.ts failed the trusted command identity assertion
  • restored the unsafe drain behavior that republishes every held fragment: 4 tests failed — 2 scanner contract failures plus the local-provider and daemon-session exit leak guards
  • suppressed a live focused hook when trusted command evidence exists: use-tab-agent-command-identity.test.ts failed the new conflicting-evidence assertion
  • swapped live and completed sibling fallback rungs: use-tab-agent.test.ts failed the new conflicting-sibling assertion

The typecheck gate was also proven live with a temporary number-to-string assignment; pnpm tc failed with TS2322 and TS6133 before the probe was removed and the clean gate reran successfully.

Identity coverage limits

Fish and PowerShell marker payloads carry the typed command line; aliases and functions are not resolved in those hooks. Typing cc where cc aliases codex therefore does not produce codex identity. zsh is the only shell path in this PR that uses the alias-expanded preexec argument.

Identity recognition is intentionally first-token and known-entrypoint based. A wrapper script such as ~/bin/myagent that execs codex remains unrecognizable even when it ultimately resolves to codex; invoke the canonical codex command or an already-supported interpreter entrypoint for identity.

SSH marker pass-through is unchanged and expected: this PR changes zero SSH and zero relay files, so arbitrary remote marker-shaped bytes are not scrubbed by this slice.

@coderabbitai

coderabbitai Bot commented Aug 28, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This change adds OSC 777 shell command markers for Bash, Zsh, Fish, and PowerShell. It adds nonce-based injection policy, command decoding, shell identity v2 markers, and WSL propagation. PTY providers and daemon sessions scan markers and emit command-started facts. Runtime paths forward these facts to clients. The renderer stores pane command identity and includes trusted or untrusted command evidence in tab agent resolution. Tests cover launch policy, shell wrappers, scanners, live shells, runtime propagation, and renderer state.

Merge Risk: 🔵 Low · up to 42cfe

The change adds local shell command identity markers and keeps them display-only, but a few bounded correctness issues remain around fish command lifecycle reporting, unusual command text, snapshot boundaries, and identity behavior during recovery. The PR is mergeable with explicit owner awareness and follow-up on these cases.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.85% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 52 functions across 57 files. (2 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The description provides detailed scope, rationale, safety notes, and testing results, but it omits several required template sections, including a Linked Issue, Visual Proof or an explicit N/A statem… Add the missing required sections. Provide a linked issue after "Fixes #"; add visual proof or state N/A with a reason; complete AI Disclosure, Review, Agent skill upstream boundary, Notes, and Checklist. Preserve the existing ELI5, scope, …
✅ Passed checks (3 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title is concise, specific, and accurately describes the main change: adding local shell command identity markers.
Full details: Docstring Coverage

Explanation

Docstring coverage is 28.85% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 52 functions across 57 files. (2 skipped: 2 unsupported.)

Full details: Description check

Explanation

The description provides detailed scope, rationale, safety notes, and testing results, but it omits several required template sections, including a Linked Issue, Visual Proof or an explicit N/A statement, AI Disclosure, Review, Agent skill upstream boundary, Notes, and the Checklist.

Resolution

Add the missing required sections. Provide a linked issue after "Fixes #"; add visual proof or state N/A with a reason; complete AI Disclosure, Review, Agent skill upstream boundary, Notes, and Checklist. Preserve the existing ELI5, scope, safety, and testing details.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

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
brennanb2025 force-pushed the brennanb2025/identity-slice1a branch from 7fe8475 to b6b7144 Compare August 28, 2026 10:05

@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: 6

Caution

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

⚠️ Outside diff range comments (1)
src/main/providers/windows-shell-args.ts (1)

32-42: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Preserve the Codex preflight when the Git Bash wrapper is unavailable. If ensureShellReadyWrappersAt() fails, the fallback starts Bash without the rcfile that contains getPosixCodexShellLaunchPreflight(). codexLaunchPreflightCommand is ignored on this path, so Codex can skip agent hooks prepare-codex.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: da25bfe4-9ab0-4754-aff3-e1f99483bb6d

📥 Commits

Reviewing files that changed from the base of the PR and between 65dd06a and b6b7144.

📒 Files selected for processing (86)
  • .github/workflows/pr.yml
  • config/scripts/pr-code-change-scope.mjs
  • src/main/__fixtures__/shell-wrapper-snapshots/daemon-bash-rcfile.txt
  • src/main/__fixtures__/shell-wrapper-snapshots/daemon-fish-init.txt
  • src/main/__fixtures__/shell-wrapper-snapshots/daemon-zsh-zshenv.txt
  • src/main/__fixtures__/shell-wrapper-snapshots/local-bash-rcfile.txt
  • src/main/__fixtures__/shell-wrapper-snapshots/local-fish-init.txt
  • src/main/__fixtures__/shell-wrapper-snapshots/local-zsh-zshenv.txt
  • src/main/daemon/daemon-bash-shell-ready-rcfile.ts
  • src/main/daemon/daemon-server.ts
  • src/main/daemon/daemon-stream-events.ts
  • src/main/daemon/daemon-zsh-shell-ready-wrapper-spec.ts
  • src/main/daemon/pty-subprocess-env-inheritance.test.ts
  • src/main/daemon/pty-subprocess.ts
  • src/main/daemon/pty-subprocess/native-pty-spawn.ts
  • src/main/daemon/pty-subprocess/shell-launch-plan.ts
  • src/main/daemon/pty-subprocess/subprocess-handle.ts
  • src/main/daemon/session-command-marker-intake.ts
  • src/main/daemon/session-ingress-pipeline.ts
  • src/main/daemon/session-options.ts
  • src/main/daemon/session-snapshot-access.ts
  • src/main/daemon/session-subprocess-handle.ts
  • src/main/daemon/session-terminal-control.test.ts
  • src/main/daemon/session.ts
  • src/main/daemon/shell-ready-bash-wrapper.test.ts
  • src/main/daemon/shell-ready.test.ts
  • src/main/daemon/shell-ready.ts
  • src/main/daemon/terminal-host-options.ts
  • src/main/daemon/terminal-host-session-create.ts
  • src/main/daemon/terminal-host.ts
  • src/main/fish-command-marker-emission.live-shell.test.ts
  • src/main/ipc/pty/provider/local-configure.ts
  • src/main/powershell-osc133-bootstrap.test.ts
  • src/main/powershell-osc133-bootstrap.ts
  • src/main/providers/__tests__/shell-ready-framework/shell-script-test.ts
  • src/main/providers/local-pty-provider-io-events.test.ts
  • src/main/providers/local-pty-provider.ts
  • src/main/providers/local-pty-shell-ready-bash-rcfile.ts
  • src/main/providers/local-pty-shell-ready-test-harness.ts
  • src/main/providers/local-pty-shell-ready-wrapper-fileset.ts
  • src/main/providers/local-pty-shell-ready-wrapper-generation.test.ts
  • src/main/providers/local-pty-shell-ready.ts
  • src/main/providers/local-pty-utils-windows-fallback.test.ts
  • src/main/providers/local-pty-utils.ts
  • src/main/providers/pty-provider-events.ts
  • src/main/providers/windows-shell-args.test.ts
  • src/main/providers/windows-shell-args.ts
  • src/main/pty/wsl-orca-env.test.ts
  • src/main/pty/wsl-orca-env.ts
  • src/main/runtime/orca-runtime.ts
  • src/main/shell-command-marker-launch-policy.test.ts
  • src/main/shell-command-marker-scanner.test.ts
  • src/main/shell-command-marker-scanner.ts
  • src/main/shell-command-marker-template.ts
  • src/main/shell-integration-injection-policy.test.ts
  • src/main/shell-integration-injection-policy.ts
  • src/main/shell-startup-feature-channel.test.ts
  • src/main/shell-startup-features.ts
  • src/main/shell-startup-identity-scanner.test.ts
  • src/main/shell-startup-identity-scanner.ts
  • src/main/shell-startup-launch-intent-fixtures.ts
  • src/main/shell-templates.ts
  • src/main/shell-wrapper-generated-file-snapshot.test.ts
  • src/main/zsh-scoped-histfile.live-shell.test.ts
  • src/main/zsh-startup-hook-pty-harness.ts
  • src/main/zsh-startup-hook-user-config-equivalence.live-shell.test.ts
  • src/main/zsh-startup-wrapper-builder.ts
  • src/renderer/src/components/terminal-pane/parked-terminal-byte-watcher.test.ts
  • src/renderer/src/components/terminal-pane/parked-terminal-byte-watcher.ts
  • src/renderer/src/components/terminal-pane/pty-connection-test-store-fixtures.ts
  • src/renderer/src/components/terminal-pane/pty-connection-test-store-state.ts
  • src/renderer/src/components/terminal-pane/pty-connection/terminal-keydown-fit.ts
  • src/renderer/src/components/terminal-pane/terminal-side-effect-facts-handler.test.ts
  • src/renderer/src/components/terminal-pane/terminal-side-effect-facts-handler.ts
  • src/renderer/src/lib/tab-agent-signal-identity.ts
  • src/renderer/src/lib/use-tab-agent-command-identity.test.ts
  • src/renderer/src/lib/use-tab-agent.ts
  • src/renderer/src/store/index.ts
  • src/renderer/src/store/slices/pane-command-identity.test.ts
  • src/renderer/src/store/slices/pane-command-identity.ts
  • src/renderer/src/store/slices/store-test-helpers.ts
  • src/renderer/src/store/types.ts
  • src/shared/pane-agent-identity-inventory.test.ts
  • src/shared/terminal-side-effect-facts.ts
  • src/shared/wsl-login-shell-command.ts
  • tests/e2e/paired-runtime-rejected-input-remount.unit.test.ts

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

Comment thread src/main/shell-startup-features.ts Resolved
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Review pass — CI diagnosed and fixed, plus one dead code path

CI was red at b3c3b65bd7 (11 failing checks: tests node 24/26 shards 1, 2, 3, 7, 8 and the verify rollup). All of it was authored here, none inherited. verify is only a rollup of the test jobs, so there was really one failing gate, not two.

CI root causes

Failure Cause
clearPaneCommandIdentity is not a function (4 suites) Four hand-rolled renderer store doubles never got the new pane-command-identity slice. The shared createTestStore() helper was updated; these four are separate hand-written objects that end in as StoreState, so TypeScript could not see the gap.
Cannot read properties of undefined (reading 'tab-1:…') Same doubles missing paneCommandIdentityByPaneKey.
pane-agent-identity-inventory ratchet resolveCompatibleAgentTypeForOwner moved to the new tab-agent-signal-identity.ts; the inventory still pointed at use-tab-agent.ts.
pty-subprocess-env-inheritance ORCA_SHELL_FEATURES now carries markers on every startup-command pane; the expectation still said history.

Fixed each at the cause. The store doubles now mirror the real slice's stale-epoch and ptyId guards, so they cannot accept a write the real store would drop.

A dead code path the tests could not see

Every fish pane silently emitted no marker at all. getFishCommandMarkerInitCommand() declared the nonce with set -l, which scopes it to the sourced init file — that scope is gone by the time fish_preexec fires, so __orca_command_markers_allowed read an empty nonce and returned 1 on every command. The generated script looked correct, which is exactly why the snapshot and text assertions stayed green.

Confirmed on a real fish 4.8.1 PTY before and after — MARKER_FOUND=NO → MARKER_FOUND=YES with the right nonce and command — and reduced to set -g.

Added src/main/fish-command-marker-emission.live-shell.test.ts, which drives the real selectShellStartupFeatures + getShellLaunchConfig decision through a real fish and asserts the marker, its nonce, its decoded command, and its ordering before OSC 133;C. Reverting the one-character scope change turns it red; it is wired into the shell contracts lane, and the marker sources are now in that lane's path scope (they were not, so a regression there would not have triggered the job).

PowerShell: emission was not gated on the same signal as stripping

CommandMarkersAllowed checked the integration context or the multiplexer heuristic, but never required the context to be present at all. The PTY authority only installs its scanner when it set ORCA_SHELL_INTEGRATION_CONTEXT, and getPowerShellOsc133Bootstrap() is installed unconditionally at all four call sites — so a pane with markers disabled by the per-host-class kill switch would still emit orca-cmd rows with no scanner to strip them, painting them into the user's terminal. The guard now requires the same signal the scanner gates on, which keeps the deliberate Windows 10 nonce-less untrusted path working.

This one is reasoned from the code and pinned by a generated-text test; I could not execute PowerShell on this host, so it is unverified on a real Windows box. Worth a second look from someone who can run it.

Rebase

Rebased onto current main (49 commits ahead, including #16893, which touches the same fish fixtures). Both changes survive: main's __orca_codex_type capture and this branch's nonce fix coexist in the generated init.

That rebase also pushed use-tab-agent.ts to 303 counted lines against the renderer's 300 ceiling — main grew the file and this branch's command-identity rung tipped it over. Split titleShowsNoAgent + resolveLaunchedAgentExitEvidence into tab-agent-exit-evidence.ts rather than adding a suppression.

Verified

  • CI green — 29 passed, 0 failing, 0 pending at 28f2d40191. A fresh run is in flight for the PowerShell commit 2f3f4ccdd4.
  • pnpm tc clean; full-repo oxlint clean; max-lines ratchet clean with no new bypasses; zustand fan-out and React Doctor clean.
  • ~9,400 tests green across renderer, store, providers, daemon, shared and main.
  • Wire compatibility: no relay file changed and decodeTerminalStreamFrame is untouched, so this is not a new stream opcode. Markers are injected and stripped in the same process — only the local provider and the daemon inject a nonce, and both construct a scanner — so no marker byte can reach a peer. The new command-started variant is additive on the existing transient-fact union, and applyLiveFact on main has no default branch and does not throw, so an older client ignores it and falls back to the previous identity rungs.
  • Live app check (dev build, CDP): a local bash pane reported NONCE_PRESENT=[yes] CTX=[direct], seven commands ran with zero marker bytes painted, a recognized agent command produced {agent: "codex", trusted: true, commandEpoch: 6}, the tab icon switched to the codex glyph while it was in flight, and the entry cleared on exit.
  • Performance: the scanner is linear — 0.16 ms for a 195 KB chunk with 6,400 escape sequences. No hot-path problem.

Not fixed, worth your call

  • The marker costs ~2.8 ms per command. $(printf … | base64 | tr -d '\r\n') forks three processes on every command in every wrapped bash/zsh pane (measured on macOS; a builtin-only substitution is 0.74 ms). Dropping the tr fork via parameter expansion is byte-identical output and saves ~1.5 ms, but that is under a third of a cost dominated by the base64 fork itself. The larger question — whether the command needs base64 at all rather than in-shell escaping of the framing characters — is a design decision I did not want to make inside a review pass.
  • getGitBashLaunchCommand(_codexLaunchPreflightCommand?) no longer uses its parameter (the preflight moved into the wrapper rcfile). Behaviourally correct, but the parameter and the argument at the call site are now dead.
  • Untrusted (forgeable) command evidence ranks below the title rung and only fills a gap, which is a sound choice — but it does mean cat-ing an attacker-controlled file containing a well-formed marker can influence a tab's agent label when nothing else identifies it. Flagging it as a deliberate trade-off rather than a defect.

Not verified

Windows and WSL were not exercised at runtime — no Windows host in this session. Dev-mode CDP is structurally blind to packaged-build differences, so the live check above covers the dev build only. The remote/SSH path was reasoned about from the code (no nonce injection on any relay provider) but not executed.

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Correction, and the real fish bug

I got the headline of my last comment wrong. Correcting it before anything else.

I claimed every fish pane emitted no marker because set -l scoped the nonce to the sourced init file. That is not what happens. The launcher passes the init text straight to fish -C (local-pty-shell-ready.ts:186, daemon/shell-ready.ts:235), and the event handler reaches that top-level scope, so set -l worked. My harness wrote the init to a file and did -C 'source <file>' — a different scope — so the "red" I reported was my test's artifact, not a product defect. Verified both ways against real fish 4.8.1.

The set -g change stays, because it states the requirement instead of depending on -C scoping, but it fixed nothing and I should not have described it as a P0. The test now drives the real -C form.

The actual fish bug is on that same line

| command base64 | string collect | string replace -a '\n' '' | string replace -a '\r' ''

fish single quotes do not interpret escapes, so '\n' is the two characters backslash-n and the replace never matches a newline:

$ fish -c "printf 'a\nb' | string replace -a '\n' X"
a
b        # unchanged

GNU coreutils base64 wraps at 76 columns; macOS base64 does not. So on every Linux, WSL and SSH fish host, any command longer than 57 bytes produced a payload with whitespace in it, the scanner's base64 test rejected it, and the marker was silently dropped. macOS is the one platform where this looks fine — which is why it survived the snapshots and would have survived my first test too.

Fixed by mirroring bash/zsh (| command tr -d '\r\n'; tr does interpret the escapes). The regression test installs a 76-column-wrapping base64 ahead of the real one so a macOS run exercises the Linux shape, and it goes red against the previous pipeline.

Three more, all with executable repros

zsh had no emulate -L zsh in __orca_osc133_preexec. Under setopt ksharrays the unbraced $+commands[base64] parses as arithmetic and zsh prints bad output format specification before every command, while ${1[1,4096]} turns 0-based so claude is captured as laude and recognition fails. Its own sibling __orca_deferred_init already carries that guard with a comment naming KSH_ARRAYS. Verified on zsh 5.9, and verified fixed: the full claude --resume now decodes with no error line.

The scanner republished the nonce. MAX_ENCODED_CHARS was computed as 4096 * 4/3, but every emitter truncates in characters, not bytes. A 4096-character CJK command is 16384 base64 chars against a 5469 cap → decode fails → the raw row, nonce included, was forwarded into the pane's own byte stream. Measured: NONCE_LEAKED=true. Anything reading that stream — including an agent running in the pane — could then mint trusted: true. An undecodable private row is now dropped rather than forwarded, and the cap accounts for 4-byte UTF-8.

This replaces the assertion at shell-command-marker-scanner.test.ts:44 that pinned byte-for-byte preservation. Flagging that explicitly rather than quietly flipping it: nothing outside Orca's own wrapper may emit this private prefix, so forwarding an undecodable row only ever published the nonce.

An over-cap unterminated candidate discarded the rest of the chunk. Both branches break, and the over-cap branch assigns input = candidate.slice(1) which is never read again. Measured: 9017 characters in, 1 out, 9016 lost. In the daemon this also desyncs rawCursor, corrupting outputSequence and every hidden-output-restore offset after it. Now continues. Added a split-fuzz test across every chunk boundary asserting byte-for-byte reassembly — that is what would have caught it.

awk in the startup identity block was unguarded while every sibling around it has 2>/dev/null || true. On an awk-less image that prints an error at startup, and under set -e it aborts the rest of the rcfile. Guarded.

Still open — your call, not mine

  • markers is an overloaded feature name. It already gates the entire OSC 133 machinery in the wrapper (local-zsh-zshenv.txt:86-91), and the new code filters it out whenever the command-marker policy is off. Repro with ORCA_DISABLE_SHELL_COMMAND_MARKERS_DAEMON_NATIVE=1: zsh goes from history,markers to history (no OSC 133 at all) and bash goes to fully unwrapped. So the kill switch removes command-finished, prompt marks, exit codes, and this PR's own clear-on-finish. Both production callers pass a nonce, so it is kill-switch-only — but the escape hatch makes things strictly worse. The fix is a distinct command-markers feature; that is a design change I did not want to make unilaterally.
  • Every bash and Git Bash pane stops being a login shell. injectsCommandMarkers: true is hardcoded at both call sites, so panes previously unwrapped are now wrapped: shopt -q login_shell becomes false and ~/.bash_logout no longer runs. The rcfile does reproduce the login chain, and the PR deletes the comment that existed to guard this equivalence. Materially broader than "add identity markers" — worth an explicit decision.
  • Startup identity marker v2 (shell-templates.ts:43-57 plus its scanner) is a separable capability — shell PID, /proc start ticks, tty — with its own forks per shell start. It is wired unconditionally for bash but gated for zsh, with no stated reason. Reviewing it inside a 77-file command-identity PR is how the unguarded awk got through.
  • ~2.8 ms per command from the base64+tr forks stands (measured). No precedent forks per command; in-shell escaping of the framing characters would remove both the cost and the byte/character cap problem above.
  • Session.exitCode was changed from a private field with a getter to a public mutable property; nothing in the diff needs it.

Not verified

Windows and WSL were not exercised at runtime. The last CI red was native-smoke (windows-latest) failing on a 15 s timeout in ssh-remote-cli-launcher — unrelated to this PR. The PowerShell guard from my previous commit is still reasoned-only; no PowerShell on this host.

@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.

Caution

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

⚠️ Outside diff range comments (1)
src/main/shell-command-marker-scanner.ts (1)

28-28: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Count Unicode code points before rejecting the command.

TextDecoder returns a UTF-16 string, so command.length counts each astral code point twice. A command with SHELL_COMMAND_MAX_CHARS emoji code points can fit MAX_ENCODED_CHARS but is rejected here. The scanner then drops the marker instead of emitting command-started.

Use Array.from(command).length and add a regression test with '\u{1F642}'.repeat(SHELL_COMMAND_MAX_CHARS).


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3ae46597-3e65-4681-9e91-0012df65e89d

📥 Commits

Reviewing files that changed from the base of the PR and between 2f3f4cc and caf7cd5.

📒 Files selected for processing (12)
  • src/main/__fixtures__/shell-wrapper-snapshots/daemon-bash-rcfile.txt
  • src/main/__fixtures__/shell-wrapper-snapshots/daemon-fish-init.txt
  • src/main/__fixtures__/shell-wrapper-snapshots/daemon-zsh-zshenv.txt
  • src/main/__fixtures__/shell-wrapper-snapshots/local-bash-rcfile.txt
  • src/main/__fixtures__/shell-wrapper-snapshots/local-fish-init.txt
  • src/main/__fixtures__/shell-wrapper-snapshots/local-zsh-zshenv.txt
  • src/main/fish-command-marker-emission.live-shell.test.ts
  • src/main/shell-command-marker-scanner.test.ts
  • src/main/shell-command-marker-scanner.ts
  • src/main/shell-command-marker-template.ts
  • src/main/shell-templates.ts
  • src/main/zsh-startup-wrapper-builder.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/fixtures/shell-wrapper-snapshots/local-bash-rcfile.txt
  • src/main/fixtures/shell-wrapper-snapshots/daemon-bash-rcfile.txt

Included review availability: Your plan provides up to 10 included reviews per hour; 2 remain after this review.

brennanb2025 added a commit that referenced this pull request Aug 28, 2026
…marker nonce

Three review findings on #16915:

- fish emitted 133;C from fish_preexec with no matching 133;D, so a wrapped
  fish pane never reported command-finished: no clear-on-finish, no exit code,
  and every command fell back to an identity RPC instead of the D fast path.
  Adds a fish_postexec handler that captures $status first (any later command
  overwrites it) and is gated by the same markers-allowed predicate as C, so
  the pair stays balanced under tmux/screen.

- selectShellStartupFeatures restricted command markers to bash/zsh/fish, so a
  plain PowerShell pane never received a nonce and the nonce-gated orca-cmd
  emitter this PR added to the PowerShell bootstrap could never fire. PowerShell
  is wrapped unconditionally, so selecting markers changes no startup-file chain.

- the parked-terminal byte-watcher mock accepted stale writes and unconditional
  clears that createPaneCommandIdentitySlice rejects. The guards move to a shared
  test double so the mock cannot drift from the store again.

Both behaviour fixes are pinned by tests that fail without them: real fish 4.8.1
via the inline `fish -C` launch form, and a plain-PowerShell selector case.

@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


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 73e51540-40fc-4234-b6f5-32c6ff4c54a9

📥 Commits

Reviewing files that changed from the base of the PR and between caf7cd5 and 42cfe29.

📒 Files selected for processing (8)
  • src/main/__fixtures__/shell-wrapper-snapshots/daemon-fish-init.txt
  • src/main/__fixtures__/shell-wrapper-snapshots/local-fish-init.txt
  • src/main/fish-command-marker-emission.live-shell.test.ts
  • src/main/shell-command-marker-template.ts
  • src/main/shell-startup-feature-channel.test.ts
  • src/main/shell-startup-features.ts
  • src/renderer/src/components/terminal-pane/pane-command-identity-test-double.ts
  • src/renderer/src/components/terminal-pane/parked-terminal-byte-watcher.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 1 remains after this review.

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Round 2 — CI green, all four threads settled

Head 42cfe29a6e. 29 checks pass, 5 skipped, 0 failures, mergeStateStatus: CLEAN.

package (windows) — flake, not this PR

The red at caf7cd59c9 was windows-host-job.win32.test.ts:89, expected true to be false — the shell pid still alive 3s after the host was SIGKILLed. Attribution, structural first:

  • The test imports nothing from src/ — only node:fs/os/path/child_process and vitest. Its host script requires only node_modules/node-pty and that module's native conpty. No wrapper, no marker, no scanner is reachable from it, and the file is not in this PR's diff.
  • The job's vitest file list is byte-identical between the passing run at 2f3f4ccdd4 and the failing run at caf7cd59c9 (same 26 files, same order).
  • The 12-file delta between those two commits is entirely shell-marker/snapshot/template code; none of those files appear in the job's 26-file list.

Empirically, package (windows) on this branch: b6b7144128 pass, 28f2d40191 pass, 2f3f4ccdd4 pass, caf7cd59c9 fail, 42cfe29a6e pass. Across 8 contemporaneous runs on other PRs that day, 8/8 passed. So 12 observed passes to 1 failure. main never runs this workflow (PR Checks is pull_request-only), which is why there is no main-side data point.

Timing rules out a "too slow under load" story rather than supporting it: the test took 7382 ms when it passed and 7134 ms when it failed — the duration is dominated by two fixed sleeps, so it is near-constant either way. Nothing was loosened, and no timeout was touched.

Separate, pre-existing problem worth its own issue: the test polls for its setup condition (for attempt < 60 waiting on SHELL=) but asserts its teardown condition after a bare await sleep(3_000). Job-object reaping of a detached grandchild on a loaded Windows runner is not guaranteed inside any fixed window, so this is a race by construction. The fix is to poll for death with a deadline — which asserts the identical property (both pids gone) and is not a loosened assertion. Not done here: the file is outside this PR.

e2e / ssh docker watcher isolation

Resolved itself — completed success in 15m6s on the old head and 14m24s on the new one. No cancel was needed. Note #17017 (the stale private fork of connectDockerSshRelayTarget) had not merged at the time of this round, so this lane is passing without it.

Review threads

path:line verdict fix reply
__fixtures__/.../daemon-fish-init.txt:22 — fish OSC 133 lifecycle Real 42cfe29a6e r3880443864
daemon/session-snapshot-access.ts:32 — drain intake before final snapshot Not real — r3880444001
shell-startup-features.ts:62 — markers for PowerShell Real 42cfe29a6e r3880444144
parked-terminal-byte-watcher.test.ts:103 — mock vs store guards Real 42cfe29a6e r3880444272

Two are worth calling out because the labels were misleading in both directions.

fish (Major, correct). The PR made fish emit 133;C from fish_preexec with nothing emitting 133;D. main had no fish wrapper at all, so this PR introduced the unbalanced half — fish was the only wrapped shell opening the lifecycle without closing it. onCommandFinished (pane-foreground-agent-tracker.ts:284) is the sole path that clears command identity and fast-paths back to shellForeground: true, so every fish command instead fell through to an identity RPC and nothing downstream ever fired. Fixed with a fish_postexec handler that captures $status first (the allow-check is itself a command and would clobber it) and is gated by the same predicate as C, so the pair stays balanced under tmux.

Drain (Minor, wrong). commandMarkerIntake.drain() already runs at session.ts:362 — after drainAndClose(), before flushPending(), exactly the requested order. The suggested second drain site would also republish the withheld orca-cmd nonce prefix into a persisted snapshot, which is the leak fixed earlier in this PR.

Both behaviour fixes are pinned by tests that fail without them (verified by ablation): real fish 4.8.1 driven through the inline fish -C form the launcher actually uses, and a plain-PowerShell selector case.

Still yours to decide — unchanged from round 1

  1. markers is an overloaded feature name, so the kill switch disables all OSC 133, not just command markers.
  2. Every bash / Git Bash pane stops being a login shell.

I deliberately did not let any thread move these. Thread 3 touches the same selector, so for the record: adding PowerShell changes no startup-file chain — that branch already returns mode: 'wrapped' unconditionally, so it only supplies the nonce its emitter needs. It has no bearing on either open question.

Not verified

No Windows or WSL runtime in this session — the PowerShell fix is reasoned from the code plus the unit selector test, and the orca-cmd emission it unblocks was never executed. pnpm tc clean, oxlint clean on changed files, 806 tests green across the provider/daemon/shell suites locally. pnpm run check:code-quality:changed could not run here (its oxlint JSON parser crashes under Node 26); oxlint was run directly on the changed files instead.

@greptile-apps

greptile-apps Bot commented Aug 28, 2026 •

Copy link
Copy Markdown

Greptile Summary

The PR adds authenticated shell command-start markers for local, daemon, and WSL terminals and uses the resulting display-only facts when resolving tab agent identity.

  • Adds marker emission to Bash, Zsh, Fish, and PowerShell shell integrations.
  • Scans and strips private marker bytes at PTY authorities while publishing normalized command identity facts.
  • Adds renderer state and precedence rules for trusted and untrusted command evidence.
  • Expands shell startup metadata, WSL environment forwarding, and focused shell/runtime coverage.

Confidence Score: 3/5

The PR does not appear safe to merge until split-marker sequence accounting and lossy malformed-marker handling are corrected.

Split markers still inflate local PTY raw positions by charging retained bytes against a later emission, and malformed terminated marker-shaped output is still silently removed from the terminal stream.

Files Needing Attention: src/main/providers/local-pty-session-activation.ts; src/main/shell-command-marker-scanner.ts

Important Files Changed

Filename Overview
src/main/shell-command-marker-scanner.ts Implements bounded marker parsing and stripping, but still discards terminated malformed private-prefix output.
src/main/providers/local-pty-session-activation.ts Integrates marker scanning with local PTY sequence accounting, where split markers still inflate the reported raw cursor.
src/main/daemon/session-command-marker-intake.ts Integrates marker scanning into daemon ingress using persistent raw-sequence accounting.
src/main/pty/wsl-orca-env.ts Forwards shell marker feature and nonce inputs into WSL for guest-side wrapper initialization.
src/renderer/src/lib/tab-agent-signal-identity.ts Places trusted command identity below focused hooks and process evidence but above title evidence.

Sequence Diagram

sequenceDiagram
  participant S as Shell integration
  participant P as Local/daemon PTY authority
  participant R as Renderer state
  participant T as Terminal tab
  S->>P: Private nonce-bearing command marker
  P->>P: Scan, validate, and strip marker bytes
  P-->>R: Normalized agent, trust, and command epoch
  P-->>T: Ordinary terminal output only
  R->>T: Resolve agent identity by evidence precedence
Loading

Reviews (7): Last reviewed commit: "fix(shell): emit the alias-resolved comm..." | Re-trigger Greptile

Comment on lines +983 to +991
let rawCursor = emission.rawStartSeq
for (const item of commandMarkerScanner.accept(emission.data)) {
if (item.kind === 'data') {
rawCursor += item.data.length
emitProviderData(item.data, item.data.length, false)
continue
}
rawCursor += item.rawLength
emitProviderData('', item.rawLength, true, rawCursor)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Split markers corrupt sequence offsets

When a command marker spans two ingress emissions, rawCursor starts at the second emission while item.rawLength includes bytes retained from the first. This double-counts the retained prefix, reports a high-water position beyond the real stream, and can trigger incorrect gap recovery or mishandle subsequent output.

Knowledge Base Used: Terminal runtime and interaction

Comment on lines +89 to +97
const fields = candidate.slice(COMMAND_MARKER_PREFIX.length, terminatorStart).split(';')
const command =
fields.length === 2 && fields[0]!.length <= MAX_NONCE_CHARS
? decodeCommand(fields[1] as string)
: null
if (command === null) {
// Why dropped and not forwarded: nothing else may emit this private prefix, and
// the row carries the nonce — re-emitting it would publish the nonce downstream.
} else {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Malformed marker collisions lose output

A terminated OSC sequence that shares the private prefix but fails field or base64 validation is consumed without producing either terminal data or a fact. This makes the metadata parser lossy for colliding application output and can remove all text through the next BEL or ST terminator.

Knowledge Base Used: Terminal runtime and interaction

brennanb2025 added a commit that referenced this pull request Aug 29, 2026
`native-smoke (windows-latest)` fails ~4.5% of runs on
`preserves a multiline argument through the compiled remote launcher`
with "Test timed out in 15000ms" — on unrelated PRs, for reasons that
have nothing to do with them. Across 176 sampled attempts it is the only
red that job produced, and it hit seven different PRs in two days:
#16900, #16904, #16915, #16955 (twice), #16979, #17014, #17085.

The test is six process creations: powershell.exe forks csc.exe, then
the freshly compiled orca.exe forks node.exe, twice. Hosted Windows
runners periodically slow process creation down, and this test amplifies
that far harder than anything else in the job. Comparing the 80 attempts
where it ran under 3s against the 12 where it ran over 12s, its own
median goes 2198ms -> 15917ms (7.2x) while the same file's
powershell-only test moves 556 -> 686ms (1.2x), the cmd.exe and Git Bash
process tests in the neighbouring file move 1.4x, and the other 35 files
put together move 1.5x.

Measured across those 176 attempts: 1881ms to 35438ms, p50 4264ms,
correlation +0.881 with the job's total Vitest duration. 8 of 176 (4.5%)
exceeded the 15s cap; 2 of 176 (1.1%) also exceeded the shared 30s
testTimeout, so deleting the override and inheriting the config is not
enough on its own. 60s clears all 176 with 1.7x headroom on the worst.

This is slow, not hung. Every body here is synchronous spawnSync, so
Vitest cannot interrupt one — the timer fires only after the body
returns and the reported duration is real elapsed time. That is why a
failure reads `× ... 22464ms` under `Test timed out in 15000ms`. The
work finished; the stopwatch was short. Seven reruns at one identical
head measured 2053 / 4680 / 5551 / 8732 / 13506 / 14868 / 21937ms — the
last of those would have been red on code that had not changed.

The 15s came from #8897, which raised this test off Vitest's built-in 5s
default because the job then ran bare `pnpm vitest run`. #8909 landed
3h27m later and pointed the job at config/vitest.config.ts, which is the
real fix for that. The constant stayed behind and has been the binding
budget ever since.
`set -l` scoped the fish marker nonce to the sourced init file, which is
already out of scope when `fish_preexec` fires, so every fish pane failed
`__orca_command_markers_allowed` and emitted no marker at all. The generated
script looked correct, so only a live shell shows it.

Also repairs the four hand-rolled renderer store doubles that omit the new
pane-command-identity slice, the agent-identity inventory path that moved to
tab-agent-signal-identity.ts, and the env-inheritance expectation now that
`markers` rides along on every startup-command pane.

Adds a real-fish contract test (red before this change, green after) and puts
the marker sources in the shell-contracts path scope so the lane actually runs
when they change.
…b-agent

Rebasing onto current main put use-tab-agent.ts at 303 counted lines against
the renderer's 300 ceiling: main grew the file and this branch's command-identity
rung tipped it over. `titleShowsNoAgent` and `resolveLaunchedAgentExitEvidence`
are a self-contained "did the launched agent exit" question, so they move to
tab-agent-exit-evidence.ts rather than the file carrying a suppression.

No behavior change; the exit-evidence test now imports from the new module.
CommandMarkersAllowed accepted the multiplexer heuristic on its own, so it
never required ORCA_SHELL_INTEGRATION_CONTEXT to be present at all. The PTY
authority only builds its marker scanner when it set that variable, and the
OSC 133 bootstrap is installed unconditionally — so a pane whose markers were
turned off by the per-host-class kill switch would still emit orca-cmd rows
with nothing to strip them, painting them into the terminal.

Require the context in both arms. The nonce cannot be the gate here: Windows 10
deliberately emits untrusted rows with no nonce.

Reasoned from the code and pinned by a generated-text assertion; not executed
against a real PowerShell host.
…ounds

Corrects an earlier claim of mine on this branch: fish markers were NOT dead.
The launcher passes the init text straight to `fish -C`, whose top-level scope
the event handler reaches, so `set -l` worked. That earlier `set -g` is kept —
it states the requirement rather than depending on that — but it fixed nothing,
and the test that "proved" it sourced the init from a file, which is not how the
launcher runs it. The test now drives the real `-C` form.

The real fish bug is on the same line: `string replace -a '\n'` never matches a
newline, because fish single quotes do not interpret escapes. GNU coreutils
base64 wraps at 76 columns (macOS does not), so on every Linux/WSL/SSH fish host
any command over 57 bytes produced a space-bearing payload that the scanner's
base64 test rejected — markers silently dropped. Mirrors bash/zsh's `tr` instead.

zsh: __orca_osc133_preexec had no `emulate -L zsh`, so under KSH_ARRAYS the
unbraced commands[base64] test parsed as arithmetic and printed "bad output
format specification" before every command, while the capture lost its first
character. Its sibling __orca_deferred_init already guards for exactly this.

Scanner: the shells truncate in characters but the cap was computed in bytes, so
a multibyte command over ~1366 chars failed to decode and the raw row — nonce
included — was republished into the pane's own stream. An undecodable private
row is now dropped rather than forwarded, and an over-cap unterminated candidate
keeps scanning instead of discarding the rest of the chunk (9016 chars lost in
the repro). Adds a split-fuzz test over every chunk boundary.

awk in the startup identity block is now guarded like every sibling around it.
…marker nonce

Three review findings on #16915:

- fish emitted 133;C from fish_preexec with no matching 133;D, so a wrapped
  fish pane never reported command-finished: no clear-on-finish, no exit code,
  and every command fell back to an identity RPC instead of the D fast path.
  Adds a fish_postexec handler that captures $status first (any later command
  overwrites it) and is gated by the same markers-allowed predicate as C, so
  the pair stays balanced under tmux/screen.

- selectShellStartupFeatures restricted command markers to bash/zsh/fish, so a
  plain PowerShell pane never received a nonce and the nonce-gated orca-cmd
  emitter this PR added to the PowerShell bootstrap could never fire. PowerShell
  is wrapped unconditionally, so selecting markers changes no startup-file chain.

- the parked-terminal byte-watcher mock accepted stale writes and unconditional
  clears that createPaneCommandIdentitySlice rejects. The guards move to a shared
  test double so the mock cannot drift from the store again.

Both behaviour fixes are pinned by tests that fail without them: real fish 4.8.1
via the inline `fish -C` launch form, and a plain-PowerShell selector case.
…capability

fish_preexec returns before emitting OSC 133;C when base64 is unavailable, but
fish_postexec still emitted OSC 133;D. The scanner turns any D into a
command-finished fact without requiring a preceding C, and onCommandStarted is
what cancels the deferred identity drop a D schedules -- so on a fish host with
no base64 every command finish retired the pane's live agent identity with
nothing left to invalidate it.

base64 is the only condition that can stop preexec emitting C, so the same
guard makes the two markers a pair.
33a494c added the `type -q base64; or return` guard to the fish
__orca_command_finished body in shell-command-marker-template.ts but did not
regenerate the two generated-file fixtures, so
shell-wrapper-generated-file-snapshot.test.ts has been failing shard 8/8 on the
recorded-vs-generated comparison ever since.

Fixture-only: both files gain exactly the four lines the template now emits. The
snapshot still pins the whole generated file, so the assertion is unchanged in
strength -- it now records the reviewed generator output instead of the
pre-gate one.
@brennanb2025
brennanb2025 force-pushed the brennanb2025/identity-slice1a branch from b403848 to 17dd552 Compare August 30, 2026 02:00
brennanb2025 added a commit that referenced this pull request Aug 30, 2026
`native-smoke (windows-latest)` fails ~4.5% of runs on
`preserves a multiline argument through the compiled remote launcher`
with "Test timed out in 15000ms" — on unrelated PRs, for reasons that
have nothing to do with them. Across 176 sampled attempts it is the only
red that job produced, and it hit seven different PRs in two days:
#16900, #16904, #16915, #16955 (twice), #16979, #17014, #17085.

The test is six process creations: powershell.exe forks csc.exe, then
the freshly compiled orca.exe forks node.exe, twice. Hosted Windows
runners periodically slow process creation down, and this test amplifies
that far harder than anything else in the job. Comparing the 80 attempts
where it ran under 3s against the 12 where it ran over 12s, its own
median goes 2198ms -> 15917ms (7.2x) while the same file's
powershell-only test moves 556 -> 686ms (1.2x), the cmd.exe and Git Bash
process tests in the neighbouring file move 1.4x, and the other 35 files
put together move 1.5x.

Measured across those 176 attempts: 1881ms to 35438ms, p50 4264ms,
correlation +0.881 with the job's total Vitest duration. 8 of 176 (4.5%)
exceeded the 15s cap; 2 of 176 (1.1%) also exceeded the shared 30s
testTimeout, so deleting the override and inheriting the config is not
enough on its own. 60s clears all 176 with 1.7x headroom on the worst.

This is slow, not hung. Every body here is synchronous spawnSync, so
Vitest cannot interrupt one — the timer fires only after the body
returns and the reported duration is real elapsed time. That is why a
failure reads `× ... 22464ms` under `Test timed out in 15000ms`. The
work finished; the stopwatch was short. Seven reruns at one identical
head measured 2053 / 4680 / 5551 / 8732 / 13506 / 14868 / 21937ms — the
last of those would have been red on code that had not changed.

The 15s came from #8897, which raised this test off Vitest's built-in 5s
default because the job then ran bare `pnpm vitest run`. #8909 landed
3h27m later and pointed the job at config/vitest.config.ts, which is the
real fix for that. The constant stayed behind and has been the binding
budget ever since.
nwparker pushed a commit that referenced this pull request Aug 31, 2026
`native-smoke (windows-latest)` fails ~4.5% of runs on
`preserves a multiline argument through the compiled remote launcher`
with "Test timed out in 15000ms" — on unrelated PRs, for reasons that
have nothing to do with them. Across 176 sampled attempts it is the only
red that job produced, and it hit seven different PRs in two days:
#16900, #16904, #16915, #16955 (twice), #16979, #17014, #17085.

The test is six process creations: powershell.exe forks csc.exe, then
the freshly compiled orca.exe forks node.exe, twice. Hosted Windows
runners periodically slow process creation down, and this test amplifies
that far harder than anything else in the job. Comparing the 80 attempts
where it ran under 3s against the 12 where it ran over 12s, its own
median goes 2198ms -> 15917ms (7.2x) while the same file's
powershell-only test moves 556 -> 686ms (1.2x), the cmd.exe and Git Bash
process tests in the neighbouring file move 1.4x, and the other 35 files
put together move 1.5x.

Measured across those 176 attempts: 1881ms to 35438ms, p50 4264ms,
correlation +0.881 with the job's total Vitest duration. 8 of 176 (4.5%)
exceeded the 15s cap; 2 of 176 (1.1%) also exceeded the shared 30s
testTimeout, so deleting the override and inheriting the config is not
enough on its own. 60s clears all 176 with 1.7x headroom on the worst.

This is slow, not hung. Every body here is synchronous spawnSync, so
Vitest cannot interrupt one — the timer fires only after the body
returns and the reported duration is real elapsed time. That is why a
failure reads `× ... 22464ms` under `Test timed out in 15000ms`. The
work finished; the stopwatch was short. Seven reruns at one identical
head measured 2053 / 4680 / 5551 / 8732 / 13506 / 14868 / 21937ms — the
last of those would have been red on code that had not changed.

The 15s came from #8897, which raised this test off Vitest's built-in 5s
default because the job then ran bare `pnpm vitest run`. #8909 landed
3h27m later and pointed the job at config/vitest.config.ts, which is the
real fix for that. The constant stayed behind and has been the binding
budget ever since.
nwparker added a commit that referenced this pull request Aug 31, 2026
* fix(ssh): fence stale kills and retired pane replay

* fix(ssh): support cancellable interactive authentication

* fix(ssh): await remote catalog before snapshot adoption

* fix(pty): contain Windows ConPTY input failures

* fix(power): avoid redundant macOS display blocking

* perf(editor): narrow markdown override subscriptions

* fix(quick-open): close directory handles after reads

* refactor(linux): remove unused proc socket scanner

* fix(usage): apply flat Sonnet 4.6 pricing

* ci: prime Node next native test cache

* docs(skills): resolve snapshot cleanup data path

* fix(ssh): recover install locks after host reboot

* test(ssh): recognize boot-aware install locks

* test(ssh): prove previous-boot lock recovery live

* test(wire): pin pre-metadata release coverage

* fix(terminal): preserve remote tab ownership through recovery races

* test(runtime): fence replaced terminal handles in agent guard

* fix(ssh): preserve remote snapshot authority across polls

* fix(pty): contain late ConPTY output EPIPE

* test(pty): register Windows exit watcher before kill

* fix: close SSH and tab readiness race gaps

* fix(tabs): retain headless order and placeholder titles

* fix(build): avoid parallel electron-vite config race

* test(windows): avoid MSYS temp path rewriting

* test(windows): avoid killing exited PTY

* fix(pty): avoid late ConPTY input teardown race

* fix(terminal): sync reconnect error ownership after commit

* fix(runtime): use canonical worktree identity comparison

* test(ssh): assert complete cold-hydration baseline

* test(windows): invoke quoted retention fixture via PowerShell

* test(windows): read ConPTY grid through mode con

* fix(terminal): publish PTY replacements atomically

* fix(terminal): infer stale identity on reattach

* fix(terminal): fence stale pane PTY callbacks

* fix(terminal): fence stale pane binds after rebind

* fix(terminal): reject stale pane transport callbacks

* fix(terminal): fence mirrored reattach spawn callbacks

* fix(terminal): replace stale pane PTYs on remount

* fix(ci): size the Windows launcher-compile test budget from measurement

`native-smoke (windows-latest)` fails ~4.5% of runs on
`preserves a multiline argument through the compiled remote launcher`
with "Test timed out in 15000ms" — on unrelated PRs, for reasons that
have nothing to do with them. Across 176 sampled attempts it is the only
red that job produced, and it hit seven different PRs in two days:
#16900, #16904, #16915, #16955 (twice), #16979, #17014, #17085.

The test is six process creations: powershell.exe forks csc.exe, then
the freshly compiled orca.exe forks node.exe, twice. Hosted Windows
runners periodically slow process creation down, and this test amplifies
that far harder than anything else in the job. Comparing the 80 attempts
where it ran under 3s against the 12 where it ran over 12s, its own
median goes 2198ms -> 15917ms (7.2x) while the same file's
powershell-only test moves 556 -> 686ms (1.2x), the cmd.exe and Git Bash
process tests in the neighbouring file move 1.4x, and the other 35 files
put together move 1.5x.

Measured across those 176 attempts: 1881ms to 35438ms, p50 4264ms,
correlation +0.881 with the job's total Vitest duration. 8 of 176 (4.5%)
exceeded the 15s cap; 2 of 176 (1.1%) also exceeded the shared 30s
testTimeout, so deleting the override and inheriting the config is not
enough on its own. 60s clears all 176 with 1.7x headroom on the worst.

This is slow, not hung. Every body here is synchronous spawnSync, so
Vitest cannot interrupt one — the timer fires only after the body
returns and the reported duration is real elapsed time. That is why a
failure reads `× ... 22464ms` under `Test timed out in 15000ms`. The
work finished; the stopwatch was short. Seven reruns at one identical
head measured 2053 / 4680 / 5551 / 8732 / 13506 / 14868 / 21937ms — the
last of those would have been red on code that had not changed.

The 15s came from #8897, which raised this test off Vitest's built-in 5s
default because the job then ran bare `pnpm vitest run`. #8909 landed
3h27m later and pointed the job at config/vitest.config.ts, which is the
real fix for that. The constant stayed behind and has been the binding
budget ever since.

* fix(terminal): fence stale remount reattach ownership

* fix(terminal): reconcile mounted pane identity after replacement

* fix(terminal): fence stale reattach fallback ownership

* fix(terminal): fence deferred SSH reattach ownership

* fix(terminal): fence stale split pane ownership callbacks

* fix(terminal): keep stale spawns from consuming startup

---------

Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
@brennanb2025

Copy link
Copy Markdown
Contributor Author

SSH validation on real Linux host (Orca SSH provider)

Host: ssh:ssh-1788247237047-52mqab (neil-ubuntu, Linux kernel 7.0.0-28-generic), remote worktree /home/brennan/orca-orca-ssh-16915-validation-20260901, PR commit cac1a001e7. Orca dev identity was verified as Orca: brennanb2025/identity-slice1a (CDP 9334); relay build passed.

Results:

  • Normal remote pane: typed codex --version (codex-cli 0.151.0) through the visible terminal. Client pty:data contained only the typed command, output, and OSC 133; no 777;orca-cmd bytes. Visible screenshot and terminal snapshot had no marker. After running codex, the pane remained a normal shell on exit; paneCommandIdentityByPaneKey, paneForegroundAgentByPaneKey, agentStatusByPaneKey, and runtimeAgentOrchestrationByPaneKey were all empty, so SSH identity remains unchanged from main.
  • Alias: host default shell is /bin/bash; zsh is not installed (zsh: command not found), and fish, pwsh, and powershell are absent. With alias cc=claude, cc --version returned 2.1.233 (Claude Code). Raw stream carried typed text cc --version only (no private marker); tab title stayed brennan@neil-ubuntu: ~/orca-orca-ssh-16915-validation-20260901 and pane identity remained empty. Bash/fish/PowerShell marker payload behavior therefore cannot be exercised for missing shells; bash path is confirmed for this host.
  • Adversarial complete marker emitted by a remote command (ESC ]777;orca-cmd;nonce-ssh16915-full;Y29kZXg= BEL): marker and nonce were delivered verbatim in client pty:data; they were not visible in the screenshot, but getMainBufferSnapshot() still contained the marker bytes. No matching marker/nonce appeared in logs/main.trace.ndjson or logs/daemon.log.
  • Truncated-marker kill: remote command emitted ESC ]777;orca-cmd;trunc16915;Y29k, then slept; pane was killed through window.api.pty.kill. The client raw stream received both the fragment and nonce before kill. After kill hasPty=false, no snapshot remained, and no marker/nonce appeared in main or daemon logs; screenshot before kill showed no painted fragment.

Verdict: DO NOT LAND for the stated SSH safety claim. Normal Orca-injected SSH commands do not emit markers and identity is unchanged, but the explicit boundary/kill requirement is disproved: remote private marker bytes (including a truncated nonce-bearing fragment) cross into the client raw stream, and a complete marker is retained by the main buffer snapshot. The relay/SSH path is unchanged as designed, so this cannot be called a pass for “private marker bytes may not cross the SSH boundary.”

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Correction to the verdict above

The DO NOT LAND in the previous comment rests on a test that was never completed, and I believe the finding is not a regression. Recording why.

This PR touches zero relay and zero SSH files. Against origin/main, the diff contains no src/relay/ files and no SSH-path files at all. Relay byte pass-through is therefore byte-identical to main by construction — this change cannot have altered it.

That means the adversarial result — a remote printf of marker-shaped bytes reaching the client and appearing in the buffer snapshot — reproduces on main too. The decisive main-vs-branch comparison wasn't run before the verdict was posted; the structural argument settles it in the same direction.

The evidence gathered actually supports this: the bytes arrived unstripped. If this PR had modified the SSH path, they would have been stripped, because stripping is what it adds. Finding them unstripped is confirmation the SSH path is untouched.

What the adversarial case does and doesn't show

A PTY relay forwards bytes; a remote program can print any escape sequence. The consequences here were inert — not painted, and the identity/foreground store maps stayed empty, so a forged marker created no identity. There was also no real nonce to leak: nothing on an SSH host emits one, so the nonce in the probe was invented by the test.

What the validation got right, and is being fixed

1. The wording in this PR was too strong. "Neither the private marker bytes nor the pane command identity may cross the SSH boundary" overstates the guarantee. What actually holds, and what the normal-path evidence confirms: Orca does not emit markers on SSH panes, and no pane command identity is derived from remote output. Typed codex --version and alias cc=claude on a real remote pane produced no marker and left the identity maps empty.

2. A real forward risk for the next slice. When #17737 lands and the SSH path does scan markers, a hostile or careless remote program could forge one. That is precisely what the nonce is for — a forgery fails the check, is marked trusted: false, and ranks with title rather than being believed. #17737 will carry an explicit test for a forged remote marker being rejected.

3. Coverage limit, stated plainly. zsh, fish and pwsh are absent on that host, so only bash was exercised over SSH. The zsh alias fix was verified separately against a real zsh locally.

Revised verdict: land, with the wording above corrected.

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Readiness review (2026-09-01)

Verdict: LAND (no release-blocking defect found in this PR).

The PR ref was verified at cac1a001e7edbb6ba4f32365fe3b1343a0a0336f (brennanb2025/identity-slice1a). Focused shell/scanner tests passed (6 files, 44 tests), daemon/provider/renderer identity tests passed (6 files, 122 tests), pnpm tc passed, pnpm build:relay passed, and git diff --check passed. A dedicated crash/cast/retry/resource scan found no proven P0/P1 issue; the new scanner has bounded nonce/encoded-command lengths, bounded held fragments, and teardown drains. No mobile-facing surface is touched (no mobile/, relay, pairing, E2EE, or mobile RPC schema changes); the existing transientFact event is reused and old clients ignore the additive command-started fact kind.

Platform matrix

Surface Evidence / disposition
macOS local Exact PR Electron dev app launched from /Users/brennanbenson/orca/workspaces/orca/ready-16915 (CDP 9336, renderer 5174); window.api.app.getIdentity() reported devBranch: brennanb2025/identity-slice1a and the expected repo root. Through the visible terminal, codex --version; sleep 8 showed the codex tab icon while running; a forged complete OSC 777 marker was absent from the rendered terminal (stripped). Full-window screenshots captured /tmp/ready16915-local-identity-live.png and /tmp/ready16915-local-marker-strip.png. Real fish 4.8.1 live-shell tests passed, including long GNU-base64 wrapping, nonce gating, and OSC 133 C/D ordering. Real zsh alias-expanded marker tests passed.
Linux local-or-SSH Real SSH provider target ssh:ssh-1785104650217-eduhep used via orca worktree create; remote worktree checked out the PR commit. Bash pane ran alias cc='codex' then cc --version and returned codex-cli 0.151.0; no orca-cmd marker was emitted on the SSH path. Host has bash only (zsh, fish, pwsh absent), so those shell variants do not apply there.
Windows native — Git Bash Real Windows high spec paired server used. Native Git Bash terminal emitted a complete forged marker; terminal output/readback showed only the typed command and prompt, with marker bytes stripped.
Windows native — PowerShell Real PowerShell process was opened on Windows high spec; alias cc -> Get-Location executed successfully. The host execution policy rejected the user profile, so a direct Orca PowerShell bootstrap emission could not be exercised in this run. Source and focused bootstrap tests confirm the marker payload is $commandLine from PSConsoleHostReadLine (typed text), not post-alias resolution.
WSL On the same real Windows server, MSYS_NO_PATHCONV=1 wsl.exe --exec /usr/bin/printf '%s\\n' WSL16915_OK returned WSL16915_OK, proving the required Git-Bash-to-WSL argv path. The WSL env-forwarding/unit contract passed; no separate WSL pane was available in this disposable worktree.
SSH remote pane Normal remote commands and alias output remained markerless and identity-empty. An explicit remote printf of OSC 777 marker bytes reached the client unchanged, exactly as on origin/main: this PR changes zero src/relay and zero SSH transport files, so pass-through is byte-identical and is not a regression.
Daemon-backed pane Daemon marker intake, stripping, sequence accounting, teardown drain, and transient-fact forwarding are covered by the daemon/session/provider tests above. No daemon host was available in the disposable local Electron profile; the openclaw paired runtime was intentionally not used because it is down.
Reattach/reconnect No reconnect implementation or wire contract is changed by this PR; stale-PTY/epoch and reattach-side-effect authority tests passed. A live reconnect journey was not applicable to the marker-only local/daemon slice.
Mixed old-client/new-host Additive reuse of the existing transientFact event; no new stream opcode, RPC parameter, or relay field. Older clients ignore unknown command-started facts. No mobile-facing contract is touched.

Confirmed coverage limits (documented here for users)

  • Fish and PowerShell marker payloads contain the typed command line. Shell aliases/functions are not resolved in those hooks, so typing cc where cc aliases codex does not produce a codex identity; zsh is the only shell path in this PR that uses the alias-expanded preexec argument.
  • Identity recognition is intentionally first-token/known-entrypoint based. A wrapper script such as ~/bin/myagent that execs codex remains unrecognizable, even when the wrapper ultimately resolves to codex; launch through the canonical codex command (or an already-supported interpreter entrypoint) for identity.
  • The SSH marker pass-through observation is expected and pre-existing because SSH/relay files are unchanged; this slice does not claim to scrub arbitrary remote bytes.

No code changes were needed. Cleanup removed the temporary SSH/Windows worktree sessions after evidence collection; the PR worktree remains clean.

enrick-dev pushed a commit to enrick-dev/orca that referenced this pull request Oct 15, 2026
* fix(ssh): fence stale kills and retired pane replay

* fix(ssh): support cancellable interactive authentication

* fix(ssh): await remote catalog before snapshot adoption

* fix(pty): contain Windows ConPTY input failures

* fix(power): avoid redundant macOS display blocking

* perf(editor): narrow markdown override subscriptions

* fix(quick-open): close directory handles after reads

* refactor(linux): remove unused proc socket scanner

* fix(usage): apply flat Sonnet 4.6 pricing

* ci: prime Node next native test cache

* docs(skills): resolve snapshot cleanup data path

* fix(ssh): recover install locks after host reboot

* test(ssh): recognize boot-aware install locks

* test(ssh): prove previous-boot lock recovery live

* test(wire): pin pre-metadata release coverage

* fix(terminal): preserve remote tab ownership through recovery races

* test(runtime): fence replaced terminal handles in agent guard

* fix(ssh): preserve remote snapshot authority across polls

* fix(pty): contain late ConPTY output EPIPE

* test(pty): register Windows exit watcher before kill

* fix: close SSH and tab readiness race gaps

* fix(tabs): retain headless order and placeholder titles

* fix(build): avoid parallel electron-vite config race

* test(windows): avoid MSYS temp path rewriting

* test(windows): avoid killing exited PTY

* fix(pty): avoid late ConPTY input teardown race

* fix(terminal): sync reconnect error ownership after commit

* fix(runtime): use canonical worktree identity comparison

* test(ssh): assert complete cold-hydration baseline

* test(windows): invoke quoted retention fixture via PowerShell

* test(windows): read ConPTY grid through mode con

* fix(terminal): publish PTY replacements atomically

* fix(terminal): infer stale identity on reattach

* fix(terminal): fence stale pane PTY callbacks

* fix(terminal): fence stale pane binds after rebind

* fix(terminal): reject stale pane transport callbacks

* fix(terminal): fence mirrored reattach spawn callbacks

* fix(terminal): replace stale pane PTYs on remount

* fix(ci): size the Windows launcher-compile test budget from measurement

`native-smoke (windows-latest)` fails ~4.5% of runs on
`preserves a multiline argument through the compiled remote launcher`
with "Test timed out in 15000ms" — on unrelated PRs, for reasons that
have nothing to do with them. Across 176 sampled attempts it is the only
red that job produced, and it hit seven different PRs in two days:
stablyai#16900, stablyai#16904, stablyai#16915, stablyai#16955 (twice), stablyai#16979, stablyai#17014, stablyai#17085.

The test is six process creations: powershell.exe forks csc.exe, then
the freshly compiled orca.exe forks node.exe, twice. Hosted Windows
runners periodically slow process creation down, and this test amplifies
that far harder than anything else in the job. Comparing the 80 attempts
where it ran under 3s against the 12 where it ran over 12s, its own
median goes 2198ms -> 15917ms (7.2x) while the same file's
powershell-only test moves 556 -> 686ms (1.2x), the cmd.exe and Git Bash
process tests in the neighbouring file move 1.4x, and the other 35 files
put together move 1.5x.

Measured across those 176 attempts: 1881ms to 35438ms, p50 4264ms,
correlation +0.881 with the job's total Vitest duration. 8 of 176 (4.5%)
exceeded the 15s cap; 2 of 176 (1.1%) also exceeded the shared 30s
testTimeout, so deleting the override and inheriting the config is not
enough on its own. 60s clears all 176 with 1.7x headroom on the worst.

This is slow, not hung. Every body here is synchronous spawnSync, so
Vitest cannot interrupt one — the timer fires only after the body
returns and the reported duration is real elapsed time. That is why a
failure reads `× ... 22464ms` under `Test timed out in 15000ms`. The
work finished; the stopwatch was short. Seven reruns at one identical
head measured 2053 / 4680 / 5551 / 8732 / 13506 / 14868 / 21937ms — the
last of those would have been red on code that had not changed.

The 15s came from stablyai#8897, which raised this test off Vitest's built-in 5s
default because the job then ran bare `pnpm vitest run`. stablyai#8909 landed
3h27m later and pointed the job at config/vitest.config.ts, which is the
real fix for that. The constant stayed behind and has been the binding
budget ever since.

* fix(terminal): fence stale remount reattach ownership

* fix(terminal): reconcile mounted pane identity after replacement

* fix(terminal): fence stale reattach fallback ownership

* fix(terminal): fence deferred SSH reattach ownership

* fix(terminal): fence stale split pane ownership callbacks

* fix(terminal): keep stale spawns from consuming startup

---------

Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>

This branch has not been deployed

No deployments
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