Skip to content

fix(terminal-history): drop an inherited Orca fish_history so nested Orca panes stop merging worktree histories (STA-4682) - #15195

Merged
nwparker merged 3 commits into
mainfrom
nwparker/sta-4682-drop-inherited-orca-fish-history
Aug 18, 2026
Merged

nwparker merged 3 commits into
mainfrom
nwparker/sta-4682-drop-inherited-orca-fish-history

Conversation

@nwparker

@nwparker nwparker commented Aug 18, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 5 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​176 $\color{#cf222e}{\Huge{\mathbf{−}}}$​2 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​174
Prod 6 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​46 0 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​46

ELI5

fish is unusual: it broadcasts (exports) its fish_history variable to every program it starts. So if you launch Orca from a fish terminal inside another Orca, the new Orca inherits fish_history=orca_<hash-of-that-worktree> — and every fish pane in the new app, in any worktree, wrote its commands into that one worktree's history file. This PR throws away an inherited history name that Orca itself minted, while still respecting a fish_history you set yourself.

What Changed

  • New dropInheritedOrcaFishHistory(env) in src/main/fish-history-session.ts, built on the existing isSafeFishHistorySession predicate (matches both the orca_ and orca_relay_ prefixes — a relay pane should drop a desktop-minted inherited name and vice versa; neither owns the other's worktree ids).
  • Applied at every point where an inherited value could survive:
    • injectHistoryEnv (src/main/terminal-history.ts), right beside the existing unconditional delete spawnEnv.ORCA_HISTFILE, so the fish check-before-set early return can no longer honour an inherited name.
    • injectWslFishHistoryEnv (src/main/terminal-history.ts).
    • injectRelayFishHistoryEnv (src/relay/terminal-history.ts).
    • PtyHandler.buildSpawnEnv (src/relay/pty-handler.ts), unconditionally, alongside the relay's other inherited-env scrubbers (stripInheritedBuildModeEnv, stripLegacyTerminalShimEnv).
    • The history-disabled branch of LocalPtyProvider.spawn (src/main/providers/local-pty-provider.ts), which previously cleared only ORCA_HISTFILE.
    • The daemon spawn env in createPtySubprocess (src/main/daemon/pty-subprocess.ts), guarded by opts.env?.fish_history === undefined — same shape as the neighbouring removeUnspecifiedPaneIdentityEnv, so the session the adapter's injectHistoryEnv just minted (and any explicit caller value) still wins over the daemon's own inherited env.

No wire-format, protocol, or persisted-state change; this is spawn-env only.

Why

src/main/terminal-history.ts already deleted ORCA_HISTFILE unconditionally for exactly this reason (#11146), but did the opposite for fish: if (shell === 'fish' ? spawnEnv.fish_history : spawnEnv.HISTFILE) { return result }, over a spawn env seeded from stripInheritedBuildModeEnv(process.env). Measured on fish 4.7.1:

$ fish_history=orca_deadbeef fish -c 'set -S fish_history'
$fish_history: set in global scope, exported, with 1 elements
$fish_history[1]: |orca_deadbeef|
$fish_history: originally inherited as |orca_deadbeef|

So the name really is exported, and a nested Orca kept it. Result: cross-worktree history merge — the leak the feature exists to prevent.

Dropping only names that match Orca's own minted shape keeps the check-before-set contract for a user's genuine fish_history (the same reasoning the orphan sweep already documents in fish-history-session.ts). A dropped name is re-minted from this worktree's id, which is deterministic, so nothing changes for a normal (non-nested) launch.

Relay symmetry (review follow-up)

injectRelayFishHistoryEnv is called from src/relay/pty-handler.ts (spawn :1558, revive :2163) only when historyIsolationEnabled && worktreeId && basename(shell).toLowerCase().startsWith('fish'). Putting the drop only inside it left a relay pane that inherits an Orca-shaped fish_history on any other path — isolation off, no worktreeId, or a non-fish shell — still scoped to someone else's worktree, while the desktop dropped it on both branches. The drop now also runs unconditionally in buildSpawnEnv, the single choke point both spawn and revive pass through and where the relay's other inherited-env scrubbing already lives.

Two same-shape redundancies were kept deliberately, each with a comment saying so:

  • injectRelayFishHistoryEnv's drop is that function's own precondition for its check-before-set, not the caller's. It is a no-op in production now that buildSpawnEnv scrubs first.
  • injectWslFishHistoryEnv's drop is likewise a no-op today: both callers (local-pty-provider.ts:879, daemon-pty-adapter.ts:498) run injectHistoryEnv on the same env object immediately before, and that already drops. Kept because nothing but call ordering enforces it.

Linked Issue

STA-4682 (claim 5). Follow-up to #15018.

Fixes #

Visual Proof

N/A — shell environment only; no rendered UI or interaction changes.

Testing

macOS (fish 4.7.1 installed locally) — targeted vitest files plus pnpm typecheck, npx oxlint, and npx oxfmt on the changed files. WSL/relay/daemon paths are covered by unit tests, not by a live run.

npx oxlint <10 changed files>                                    # clean
pnpm typecheck                                                   # node + cli clean; the three
  # web-project failures (open-tab-search-retention.test.ts ×2,
  # useSessionRestoredBannerDismiss.test.tsx) are pre-existing on origin/main
npx vitest run --config config/vitest.config.ts \
  src/main/terminal-history.test.ts src/relay/terminal-history.test.ts \
  src/relay/pty-handler-spawn-environment.test.ts \
  src/main/providers/local-pty-provider-spawn-env.test.ts \
  src/main/daemon/pty-subprocess-env-inheritance.test.ts         # 118 passed
npx vitest run --config config/vitest.config.ts \
  src/main/fish-history-session.test.ts src/main/terminal-history-async-delete.test.ts \
  src/main/terminal-history-tombstone-retry.test.ts src/main/daemon/pty-subprocess.test.ts \
  src/main/terminal-history-fish-session.node-pty.test.ts        # 56 passed (neighbours, unchanged)

Vacuity check — each source change reverted in turn with the new tests kept:

Test Without fix With fix
terminal-history › replaces a desktop fish_history inherited from a parent Orca FAIL PASS
terminal-history › replaces a relay fish_history inherited from a parent Orca FAIL PASS
terminal-history › drops an inherited fish_history even when the shell is not fish FAIL PASS
injectWslFishHistoryEnv › replaces an inherited Orca fish_history with this worktree session FAIL PASS
relay › replaces an inherited relay fish_history FAIL PASS
relay › replaces an inherited desktop fish_history FAIL PASS
PtyHandler › isolation off › a relay-minted session inherited from the relay process env FAIL PASS
PtyHandler › isolation off › a desktop-minted session inherited from the relay process env FAIL PASS
PtyHandler › isolation off › drops a desktop-minted session handed over in the client env FAIL PASS
LocalPtyProvider › history isolation off: an inherited Orca session FAIL PASS
createPtySubprocess › drops an inherited Orca fish_history FAIL PASS
(paired preservation cases: preserves a caller-supplied fish_history ×3, a user value, keeps a user value, keeps the session this spawn injected, keeps a caller-supplied value) PASS PASS

The preservation half of each pair passing in both states is the point: the fix must not turn into "always overwrite fish_history".

  • I manually tested these changes locally (verified fish 4.7.1 exports an inherited fish_history, the premise of the bug)
  • Automated tests added/updated

AI Disclosure

Claude Opus.

Review

Worth a close look at the daemon guard in pty-subprocess.ts: it deliberately trusts opts.env.fish_history (which is where daemon-pty-adapter's injectHistoryEnv writes the isolated session) and only scrubs the daemon's own inherited process.env value.

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

Deliberately out of scope — STA-4682's other claims are not in this PR:

  • Claim 1 (relay fish dir resolved from process env): investigated and found unreachable. buildSpawnEnv = relay process.env + renderer env + augmenters, and no writer puts HOME/XDG_DATA_HOME into a pane spawn env, so the spawn env's fish data dir cannot differ from the relay process's.
  • Claim 4 (nounset and $fish_pid): a documented, measured tradeoff at src/shared/tui-agent-startup-shell.ts:323-325; the naive alternative was re-tested and still fails (fish 4 unset prints an uncaught unknown-command diagnostic).
  • Claims 2 and 3: tracked separately.

Same class, different variable — noticed while here and not fixed, each a follow-up rather than claim-5 scope:

  • An Orca-minted HISTFILE inherited from a parent Orca pane. injectHistoryEnv's non-fish check-before-set returns early when spawnEnv.HISTFILE is already set (src/main/terminal-history.ts:213), leaving ORCA_HISTFILE unset and the pane pointed at the inherited path. HISTFILE reaches a pane through the spawn env, so zsh and bash mark it exported and a nested Orca inherits the launching worktree's history file — the same shape as this fish bug, and nothing on the spawn path strips it. A separate PR on this branch stack covers the zsh/HISTFILE side, so fixing it here would collide.
  • Daemon ORCA_HISTFILE. The daemon has no equivalent scrub for an inherited ORCA_HISTFILE when history isolation is off or the session is a reattach.

Cross-platform / SSH / mobile / perf: the change is a regex test over one env var at spawn time (no I/O, no new syscalls). WSL and relay paths get the same treatment as native. Folder workspaces and git worktrees are indistinguishable here — everything keys off worktreeId as before. Backwards compatible: session names are a pure function of the worktree id, so a re-minted name is byte-identical to the one a non-nested launch already used.

Checklist

  • This PR is small and focused
  • I explained what changed and why (including ELI5)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred) — ran oxlint + oxfmt + pnpm typecheck + targeted vitest locally; full suite left to CI

fish EXPORTS `fish_history`, so an Orca launched from a fish pane keeps the
launching worktree's session name in process.env. Every fish pane of the nested
app then hit the check-before-set early return and wrote into that one
worktree's history file, in every worktree. Drop Orca-minted names (desktop and
relay prefixes) wherever the session is injected, and in the history-disabled
and daemon spawn paths; a genuine user value still wins.
@coderabbitai

coderabbitai Bot commented Aug 18, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: dbf1aed8-d8b3-49f7-980d-fa702c0b80ea

📥 Commits

Reviewing files that changed from the base of the PR and between 6789352 and a4cbe1c.

📒 Files selected for processing (4)
  • src/main/terminal-history.ts
  • src/relay/pty-handler-spawn-environment.test.ts
  • src/relay/pty-handler.ts
  • src/relay/terminal-history.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/relay/terminal-history.ts
  • src/main/terminal-history.ts

Included review availability: Your plan includes up to 10 reviews per rolling hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The change adds shared cleanup for inherited Orca-managed fish_history values. PTY subprocess, local PTY provider, and relay PTY setup now apply this cleanup under their spawn conditions. Desktop, WSL, and relay history injection now clear inherited session values before assigning history or preserving explicit caller values. Tests cover inherited, generated, caller-supplied, and non-fish shell environments.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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 clearly describes the main fix: removing inherited Orca fish_history values to prevent nested panes from merging worktree histories.
Description check ✅ Passed The description covers the required sections, explains the issue and solution, documents testing, and records scope and compatibility considerations.
✨ Finishing Touches
📝 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.

@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: 833e82dd-2294-43c8-810f-1f043fff9842

📥 Commits

Reviewing files that changed from the base of the PR and between 6ee265e and 6789352.

📒 Files selected for processing (9)
  • src/main/daemon/pty-subprocess-env-inheritance.test.ts
  • src/main/daemon/pty-subprocess.ts
  • src/main/fish-history-session.ts
  • src/main/providers/local-pty-provider-spawn-env.test.ts
  • src/main/providers/local-pty-provider.ts
  • src/main/terminal-history.test.ts
  • src/main/terminal-history.ts
  • src/relay/terminal-history.test.ts
  • src/relay/terminal-history.ts

Included review availability: Your plan includes up to 10 reviews per rolling hour; 7 remain after this review.

Comment on lines +192 to +195
// Why here too: fish EXPORTS `fish_history`, so the same nesting hands this
// process the LAUNCHING worktree's session name — and the check-before-set
// below would honour it, writing every pane's history into that worktree.
dropInheritedOrcaFishHistory(spawnEnv)

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 | 🟠 Major | 🏗️ Heavy lift

Preserve explicitly supplied Orca-format fish_history values. Session syntax does not establish ownership. The daemon already preserves explicit opts.env.fish_history, but these paths remove the same value after environment merging.

  • src/main/terminal-history.ts#L192-L195: add explicit-value provenance to injectHistoryEnv and injectWslFishHistoryEnv; clean only inherited values.
  • src/main/providers/local-pty-provider.ts#L887-L889: preserve args.env.fish_history when history isolation is disabled, and pass its ownership into the enabled-history path.
  • src/relay/terminal-history.ts#L125-L128: pass caller ownership into injectRelayFishHistoryEnv before cleanup.
  • src/main/providers/local-pty-provider-spawn-env.test.ts#L191-L211: add an args.env.fish_history: 'orca_c0ffee' case.
  • src/main/terminal-history.test.ts#L382-L445: test explicit desktop and relay session-format values separately from inherited values.
  • src/relay/terminal-history.test.ts#L48-L68: test an explicit orca_<hash> value after the API carries ownership information.
📍 Affects 3 files
  • src/main/terminal-history.ts#L192-L195 (this comment)
  • src/main/providers/local-pty-provider.ts#L887-L889
  • src/relay/terminal-history.ts#L125-L128

…STA-4682)

injectRelayFishHistoryEnv runs only for a fish pane with history isolation
on and a worktreeId, so a relay that inherited an Orca-minted fish_history
kept it on every other path — scoping those panes to another worktree's
history file. The desktop drops it on both branches; scrub it in
buildSpawnEnv so relay spawn and revive match.

Also record why injectWslFishHistoryEnv keeps its own drop (redundant with
both current callers, kept as the function's precondition).
@nwparker
nwparker merged commit 3d29a26 into main Aug 18, 2026
100 of 101 checks passed
nwparker added a commit that referenced this pull request Aug 18, 2026
… across Orca instances

The relay-specific gate added last round rested on a false premise:
main's hasOverlayRestoreEnv already included ORCA_REMOTE_CLI_BIN_DIR, and
ssh-pty-spawn-env sets that on every SSH pane whose session has a CLI
bridge — so ordinary remote zsh panes were already wrapped. The gate only
bit where remoteCliBridgeEnv is null (a host too old to report its
platform), where it silently dropped that pane's worktree history. All
three transports now share the features.length rule.

HISTFILE stays exported, so a newly wrapped pane handed the worktree
history path to every child, including a nested Orca whose panes then all
hit injectHistoryEnv's check-before-set and appended into the launching
worktree's file. Same class as the fish_history fix in #15195: recognise a
path Orca minted and drop it before the check, on the desktop, daemon and
relay injection paths and both history-disabled branches.

Also: run the epilogue from the wrapper .zshrc when zsh is in sh/ksh
emulation, since sourcehome() then reads $HOME/.zlogin and the wrapper's
.zlogin never runs; track fallback launch-env keys per attempt rather than
once from the primary; and guard the cross-file epilogue call so a wrapper
dir shared by two builds degrades quietly.
nwparker added a commit that referenced this pull request Aug 18, 2026
…ive feature channel (#15258)

* fix(shell): repair worktree HISTFILE in plain zsh panes via one positive feature channel

A plain zsh pane — no startup command, no agent overlay — was never wrapped, so
Orca's HISTFILE repair never ran in it. macOS `/etc/zshrc` assigns
`HISTFILE=${ZDOTDIR:-$HOME}/.zsh_history` with no check-before-set and runs
before any file Orca controls, so per-worktree history was a silent no-op for
every ordinary pane on the primary platform.

Wrapping those panes needs a way to say which wrapper features a shell should
turn on. That channel is one exported variable, ORCA_SHELL_FEATURES, carrying a
comma-separated positive allowlist from a closed set (history, markers, ready,
identity, overlay). The wrapper .zshenv reads it into a plain, non-exported
array and unsets it in its first executable lines, before the user's own
.zshenv — so the selection survives .zshenv -> .zprofile -> .zshrc -> .zlogin in
this process but physically cannot reach a child. There is no negative or
suppression variable anywhere; an absent or inherited value can only ever mean
fewer features. ORCA_HISTFILE is consumed and destroyed the same way, which
removes the root cause of #11146 instead of patching it.

All order-sensitive wrapper work now lives in one `__orca_shell_epilogue`
defined in .zshenv and invoked exactly once, from .zshrc for a non-login shell
and .zlogin for a login shell, with each feature an independent guard.

Selection is a pure function of spawn env and launch intent, so a pane wrapped
only for history gets no OSC 133 and is observably identical to the unwrapped
pane it used to be.

Generation is now fail-closed: wrapper files are written to a temp name and
renamed, every required path is verified non-empty, and ZDOTDIR is only set when
that holds. Previously a failed write still pointed ZDOTDIR at an empty dir and
the user silently lost their entire zsh config.

Orca also recognised only its own `*/shell-ready/zsh` dir shape when deciding
what the user's ZDOTDIR was, so being launched from any other terminal that had
hijacked ZDOTDIR captured that as the user's config dir. Ownership is now
established positively — a stamped marker file, or Orca's own dir shape for
wrappers written by older builds — and an inherited ZDOTDIR holding no zsh
startup file is ignored. No vendor is detected by name.

* fix(shell): make the zsh epilogue option-proof and stop history widening relay wrapping

Review follow-ups on the feature-channel PR.

- `emulate -L zsh` as the epilogue's first statement. It runs after the user's
  own config, so `setopt no_unset` made the precmd_functions append a fatal
  error that returned from the whole function (no ready widget, ZDOTDIR left at
  Orca's wrapper dir), and `setopt ksh_arrays` made the 1-based feature
  subscript drop whichever feature is listed first.
- The `/etc/zshrc` HISTFILE repair is no longer behind the `history` guard: it
  undoes damage Orca's own ZDOTDIR caused, so it must also run for a shell that
  re-enters the wrapper after the allowlist was consumed.
- The relay keeps its own wrapping gate. Its .zshenv resolves the user's config
  dir from a ZDOTDIR Orca has already overwritten, so wrapping a remote pane
  just for `history` cost a relocated-ZDOTDIR user their whole shell config.
- A failed primary spawn no longer leaks the primary shell's launch env
  (wrapper ZDOTDIR + feature channel) into an unwrapped fallback pane.

* fix(shell): drop the relay wrapping gate and stop HISTFILE inheriting across Orca instances

The relay-specific gate added last round rested on a false premise:
main's hasOverlayRestoreEnv already included ORCA_REMOTE_CLI_BIN_DIR, and
ssh-pty-spawn-env sets that on every SSH pane whose session has a CLI
bridge — so ordinary remote zsh panes were already wrapped. The gate only
bit where remoteCliBridgeEnv is null (a host too old to report its
platform), where it silently dropped that pane's worktree history. All
three transports now share the features.length rule.

HISTFILE stays exported, so a newly wrapped pane handed the worktree
history path to every child, including a nested Orca whose panes then all
hit injectHistoryEnv's check-before-set and appended into the launching
worktree's file. Same class as the fish_history fix in #15195: recognise a
path Orca minted and drop it before the check, on the desktop, daemon and
relay injection paths and both history-disabled branches.

Also: run the epilogue from the wrapper .zshrc when zsh is in sh/ksh
emulation, since sourcehome() then reads $HOME/.zlogin and the wrapper's
.zlogin never runs; track fallback launch-env keys per attempt rather than
once from the primary; and guard the cross-file epilogue call so a wrapper
dir shared by two builds degrades quietly.

* fix(shell): make every wrapper file self-sufficient and retire the deleted marker vars from tests

- .zprofile/.zshrc/.zlogin each define __orca_resolve_user_config_dir. They
  called it on line 2 while only .zshenv defined it, so a wrapper dir written by
  two concurrently installed builds printed three "command not found" and
  skipped the user's entire zsh config. New live-shell test covers it.
- Retarget every remaining ORCA_SHELL_READY_MARKER/ORCA_SHELL_STARTUP_IDENTITY
  reference onto ORCA_SHELL_FEATURES, or delete it where the key is now dead.
- isOrcaMintedHistFile requires a leading '/', so a relative path of the same
  shape stays the user's.
- Drop an unused no-control-regex disable, and register the two real-zsh suites
  in the dedicated shell-contracts lane.

* fix(shell): stop the zsh wrapper colliding on REPLY and degrade under sh emulation

Widening wrapping from overlay/startup panes to every zsh pane turned three
latent wrapper defects into user-visible ones.

- The config-dir resolver used `REPLY`, zsh's shared scratch global, as its
  out-parameter. `typeset -r REPLY` in a user config made the wrapper's first
  executable assignment fatal, `typeset -i REPLY` silently resolved every path
  to 0; both left HISTFILE inside Orca's wrapper dir. It now writes an
  Orca-private `_orca_resolved_config_dir`, declared `typeset -g` so the
  rename introduces no `warn_create_global` noise. A new rule test fails on any
  generated wrapper file that writes a global outside Orca's namespace.

- The daemon dropped an inherited HISTFILE but never an inherited
  ORCA_HISTFILE, which now both wraps a pane the client scoped nothing for and
  re-exports another worktree's history path. The relay had the same gap on its
  isolation-off and revive paths. Both now mirror the desktop.

- A user .zshenv or .zprofile ending in `emulate sh` makes zsh ignore ZDOTDIR,
  so no later wrapper file is read and the epilogue never runs. Nothing can
  repair HISTFILE from there, so the wrapper now detects the emulation and
  hands the pane back unwrapped instead of leaving history somewhere invisible.

Also `typeset -g __orca_in_command` so the OSC 133 preexec hook prints no
warning under `setopt warn_create_global`.
paidaxingyo666 pushed a commit to paidaxingyo666/Manta that referenced this pull request Aug 21, 2026
…Orca panes stop merging worktree histories (STA-4682) (stablyai#15195)

* fix(terminal-history): drop an inherited Orca fish_history (STA-4682)

fish EXPORTS `fish_history`, so an Orca launched from a fish pane keeps the
launching worktree's session name in process.env. Every fish pane of the nested
app then hit the check-before-set early return and wrote into that one
worktree's history file, in every worktree. Drop Orca-minted names (desktop and
relay prefixes) wherever the session is injected, and in the history-disabled
and daemon spawn paths; a genuine user value still wins.

* fix(relay): drop an inherited Orca fish_history on every spawn path (STA-4682)

injectRelayFishHistoryEnv runs only for a fish pane with history isolation
on and a worktreeId, so a relay that inherited an Orca-minted fish_history
kept it on every other path — scoping those panes to another worktree's
history file. The desktop drops it on both branches; scrub it in
buildSpawnEnv so relay spawn and revive match.

Also record why injectWslFishHistoryEnv keeps its own drop (redundant with
both current callers, kept as the function's precondition).
paidaxingyo666 pushed a commit to paidaxingyo666/Manta that referenced this pull request Aug 21, 2026
…ive feature channel (stablyai#15258)

* fix(shell): repair worktree HISTFILE in plain zsh panes via one positive feature channel

A plain zsh pane — no startup command, no agent overlay — was never wrapped, so
Orca's HISTFILE repair never ran in it. macOS `/etc/zshrc` assigns
`HISTFILE=${ZDOTDIR:-$HOME}/.zsh_history` with no check-before-set and runs
before any file Orca controls, so per-worktree history was a silent no-op for
every ordinary pane on the primary platform.

Wrapping those panes needs a way to say which wrapper features a shell should
turn on. That channel is one exported variable, ORCA_SHELL_FEATURES, carrying a
comma-separated positive allowlist from a closed set (history, markers, ready,
identity, overlay). The wrapper .zshenv reads it into a plain, non-exported
array and unsets it in its first executable lines, before the user's own
.zshenv — so the selection survives .zshenv -> .zprofile -> .zshrc -> .zlogin in
this process but physically cannot reach a child. There is no negative or
suppression variable anywhere; an absent or inherited value can only ever mean
fewer features. ORCA_HISTFILE is consumed and destroyed the same way, which
removes the root cause of stablyai#11146 instead of patching it.

All order-sensitive wrapper work now lives in one `__orca_shell_epilogue`
defined in .zshenv and invoked exactly once, from .zshrc for a non-login shell
and .zlogin for a login shell, with each feature an independent guard.

Selection is a pure function of spawn env and launch intent, so a pane wrapped
only for history gets no OSC 133 and is observably identical to the unwrapped
pane it used to be.

Generation is now fail-closed: wrapper files are written to a temp name and
renamed, every required path is verified non-empty, and ZDOTDIR is only set when
that holds. Previously a failed write still pointed ZDOTDIR at an empty dir and
the user silently lost their entire zsh config.

Orca also recognised only its own `*/shell-ready/zsh` dir shape when deciding
what the user's ZDOTDIR was, so being launched from any other terminal that had
hijacked ZDOTDIR captured that as the user's config dir. Ownership is now
established positively — a stamped marker file, or Orca's own dir shape for
wrappers written by older builds — and an inherited ZDOTDIR holding no zsh
startup file is ignored. No vendor is detected by name.

* fix(shell): make the zsh epilogue option-proof and stop history widening relay wrapping

Review follow-ups on the feature-channel PR.

- `emulate -L zsh` as the epilogue's first statement. It runs after the user's
  own config, so `setopt no_unset` made the precmd_functions append a fatal
  error that returned from the whole function (no ready widget, ZDOTDIR left at
  Orca's wrapper dir), and `setopt ksh_arrays` made the 1-based feature
  subscript drop whichever feature is listed first.
- The `/etc/zshrc` HISTFILE repair is no longer behind the `history` guard: it
  undoes damage Orca's own ZDOTDIR caused, so it must also run for a shell that
  re-enters the wrapper after the allowlist was consumed.
- The relay keeps its own wrapping gate. Its .zshenv resolves the user's config
  dir from a ZDOTDIR Orca has already overwritten, so wrapping a remote pane
  just for `history` cost a relocated-ZDOTDIR user their whole shell config.
- A failed primary spawn no longer leaks the primary shell's launch env
  (wrapper ZDOTDIR + feature channel) into an unwrapped fallback pane.

* fix(shell): drop the relay wrapping gate and stop HISTFILE inheriting across Orca instances

The relay-specific gate added last round rested on a false premise:
main's hasOverlayRestoreEnv already included ORCA_REMOTE_CLI_BIN_DIR, and
ssh-pty-spawn-env sets that on every SSH pane whose session has a CLI
bridge — so ordinary remote zsh panes were already wrapped. The gate only
bit where remoteCliBridgeEnv is null (a host too old to report its
platform), where it silently dropped that pane's worktree history. All
three transports now share the features.length rule.

HISTFILE stays exported, so a newly wrapped pane handed the worktree
history path to every child, including a nested Orca whose panes then all
hit injectHistoryEnv's check-before-set and appended into the launching
worktree's file. Same class as the fish_history fix in stablyai#15195: recognise a
path Orca minted and drop it before the check, on the desktop, daemon and
relay injection paths and both history-disabled branches.

Also: run the epilogue from the wrapper .zshrc when zsh is in sh/ksh
emulation, since sourcehome() then reads $HOME/.zlogin and the wrapper's
.zlogin never runs; track fallback launch-env keys per attempt rather than
once from the primary; and guard the cross-file epilogue call so a wrapper
dir shared by two builds degrades quietly.

* fix(shell): make every wrapper file self-sufficient and retire the deleted marker vars from tests

- .zprofile/.zshrc/.zlogin each define __orca_resolve_user_config_dir. They
  called it on line 2 while only .zshenv defined it, so a wrapper dir written by
  two concurrently installed builds printed three "command not found" and
  skipped the user's entire zsh config. New live-shell test covers it.
- Retarget every remaining ORCA_SHELL_READY_MARKER/ORCA_SHELL_STARTUP_IDENTITY
  reference onto ORCA_SHELL_FEATURES, or delete it where the key is now dead.
- isOrcaMintedHistFile requires a leading '/', so a relative path of the same
  shape stays the user's.
- Drop an unused no-control-regex disable, and register the two real-zsh suites
  in the dedicated shell-contracts lane.

* fix(shell): stop the zsh wrapper colliding on REPLY and degrade under sh emulation

Widening wrapping from overlay/startup panes to every zsh pane turned three
latent wrapper defects into user-visible ones.

- The config-dir resolver used `REPLY`, zsh's shared scratch global, as its
  out-parameter. `typeset -r REPLY` in a user config made the wrapper's first
  executable assignment fatal, `typeset -i REPLY` silently resolved every path
  to 0; both left HISTFILE inside Orca's wrapper dir. It now writes an
  Orca-private `_orca_resolved_config_dir`, declared `typeset -g` so the
  rename introduces no `warn_create_global` noise. A new rule test fails on any
  generated wrapper file that writes a global outside Orca's namespace.

- The daemon dropped an inherited HISTFILE but never an inherited
  ORCA_HISTFILE, which now both wraps a pane the client scoped nothing for and
  re-exports another worktree's history path. The relay had the same gap on its
  isolation-off and revive paths. Both now mirror the desktop.

- A user .zshenv or .zprofile ending in `emulate sh` makes zsh ignore ZDOTDIR,
  so no later wrapper file is read and the epilogue never runs. Nothing can
  repair HISTFILE from there, so the wrapper now detects the emulation and
  hands the pane back unwrapped instead of leaving history somewhere invisible.

Also `typeset -g __orca_in_command` so the OSC 133 preexec hook prints no
warning under `setopt warn_create_global`.
dallascrilley pushed a commit to dallascrilley/orca that referenced this pull request Aug 27, 2026
…Orca panes stop merging worktree histories (STA-4682) (stablyai#15195)

* fix(terminal-history): drop an inherited Orca fish_history (STA-4682)

fish EXPORTS `fish_history`, so an Orca launched from a fish pane keeps the
launching worktree's session name in process.env. Every fish pane of the nested
app then hit the check-before-set early return and wrote into that one
worktree's history file, in every worktree. Drop Orca-minted names (desktop and
relay prefixes) wherever the session is injected, and in the history-disabled
and daemon spawn paths; a genuine user value still wins.

* fix(relay): drop an inherited Orca fish_history on every spawn path (STA-4682)

injectRelayFishHistoryEnv runs only for a fish pane with history isolation
on and a worktreeId, so a relay that inherited an Orca-minted fish_history
kept it on every other path — scoping those panes to another worktree's
history file. The desktop drops it on both branches; scrub it in
buildSpawnEnv so relay spawn and revive match.

Also record why injectWslFishHistoryEnv keeps its own drop (redundant with
both current callers, kept as the function's precondition).
dallascrilley pushed a commit to dallascrilley/orca that referenced this pull request Aug 27, 2026
…ive feature channel (stablyai#15258)

* fix(shell): repair worktree HISTFILE in plain zsh panes via one positive feature channel

A plain zsh pane — no startup command, no agent overlay — was never wrapped, so
Orca's HISTFILE repair never ran in it. macOS `/etc/zshrc` assigns
`HISTFILE=${ZDOTDIR:-$HOME}/.zsh_history` with no check-before-set and runs
before any file Orca controls, so per-worktree history was a silent no-op for
every ordinary pane on the primary platform.

Wrapping those panes needs a way to say which wrapper features a shell should
turn on. That channel is one exported variable, ORCA_SHELL_FEATURES, carrying a
comma-separated positive allowlist from a closed set (history, markers, ready,
identity, overlay). The wrapper .zshenv reads it into a plain, non-exported
array and unsets it in its first executable lines, before the user's own
.zshenv — so the selection survives .zshenv -> .zprofile -> .zshrc -> .zlogin in
this process but physically cannot reach a child. There is no negative or
suppression variable anywhere; an absent or inherited value can only ever mean
fewer features. ORCA_HISTFILE is consumed and destroyed the same way, which
removes the root cause of stablyai#11146 instead of patching it.

All order-sensitive wrapper work now lives in one `__orca_shell_epilogue`
defined in .zshenv and invoked exactly once, from .zshrc for a non-login shell
and .zlogin for a login shell, with each feature an independent guard.

Selection is a pure function of spawn env and launch intent, so a pane wrapped
only for history gets no OSC 133 and is observably identical to the unwrapped
pane it used to be.

Generation is now fail-closed: wrapper files are written to a temp name and
renamed, every required path is verified non-empty, and ZDOTDIR is only set when
that holds. Previously a failed write still pointed ZDOTDIR at an empty dir and
the user silently lost their entire zsh config.

Orca also recognised only its own `*/shell-ready/zsh` dir shape when deciding
what the user's ZDOTDIR was, so being launched from any other terminal that had
hijacked ZDOTDIR captured that as the user's config dir. Ownership is now
established positively — a stamped marker file, or Orca's own dir shape for
wrappers written by older builds — and an inherited ZDOTDIR holding no zsh
startup file is ignored. No vendor is detected by name.

* fix(shell): make the zsh epilogue option-proof and stop history widening relay wrapping

Review follow-ups on the feature-channel PR.

- `emulate -L zsh` as the epilogue's first statement. It runs after the user's
  own config, so `setopt no_unset` made the precmd_functions append a fatal
  error that returned from the whole function (no ready widget, ZDOTDIR left at
  Orca's wrapper dir), and `setopt ksh_arrays` made the 1-based feature
  subscript drop whichever feature is listed first.
- The `/etc/zshrc` HISTFILE repair is no longer behind the `history` guard: it
  undoes damage Orca's own ZDOTDIR caused, so it must also run for a shell that
  re-enters the wrapper after the allowlist was consumed.
- The relay keeps its own wrapping gate. Its .zshenv resolves the user's config
  dir from a ZDOTDIR Orca has already overwritten, so wrapping a remote pane
  just for `history` cost a relocated-ZDOTDIR user their whole shell config.
- A failed primary spawn no longer leaks the primary shell's launch env
  (wrapper ZDOTDIR + feature channel) into an unwrapped fallback pane.

* fix(shell): drop the relay wrapping gate and stop HISTFILE inheriting across Orca instances

The relay-specific gate added last round rested on a false premise:
main's hasOverlayRestoreEnv already included ORCA_REMOTE_CLI_BIN_DIR, and
ssh-pty-spawn-env sets that on every SSH pane whose session has a CLI
bridge — so ordinary remote zsh panes were already wrapped. The gate only
bit where remoteCliBridgeEnv is null (a host too old to report its
platform), where it silently dropped that pane's worktree history. All
three transports now share the features.length rule.

HISTFILE stays exported, so a newly wrapped pane handed the worktree
history path to every child, including a nested Orca whose panes then all
hit injectHistoryEnv's check-before-set and appended into the launching
worktree's file. Same class as the fish_history fix in stablyai#15195: recognise a
path Orca minted and drop it before the check, on the desktop, daemon and
relay injection paths and both history-disabled branches.

Also: run the epilogue from the wrapper .zshrc when zsh is in sh/ksh
emulation, since sourcehome() then reads $HOME/.zlogin and the wrapper's
.zlogin never runs; track fallback launch-env keys per attempt rather than
once from the primary; and guard the cross-file epilogue call so a wrapper
dir shared by two builds degrades quietly.

* fix(shell): make every wrapper file self-sufficient and retire the deleted marker vars from tests

- .zprofile/.zshrc/.zlogin each define __orca_resolve_user_config_dir. They
  called it on line 2 while only .zshenv defined it, so a wrapper dir written by
  two concurrently installed builds printed three "command not found" and
  skipped the user's entire zsh config. New live-shell test covers it.
- Retarget every remaining ORCA_SHELL_READY_MARKER/ORCA_SHELL_STARTUP_IDENTITY
  reference onto ORCA_SHELL_FEATURES, or delete it where the key is now dead.
- isOrcaMintedHistFile requires a leading '/', so a relative path of the same
  shape stays the user's.
- Drop an unused no-control-regex disable, and register the two real-zsh suites
  in the dedicated shell-contracts lane.

* fix(shell): stop the zsh wrapper colliding on REPLY and degrade under sh emulation

Widening wrapping from overlay/startup panes to every zsh pane turned three
latent wrapper defects into user-visible ones.

- The config-dir resolver used `REPLY`, zsh's shared scratch global, as its
  out-parameter. `typeset -r REPLY` in a user config made the wrapper's first
  executable assignment fatal, `typeset -i REPLY` silently resolved every path
  to 0; both left HISTFILE inside Orca's wrapper dir. It now writes an
  Orca-private `_orca_resolved_config_dir`, declared `typeset -g` so the
  rename introduces no `warn_create_global` noise. A new rule test fails on any
  generated wrapper file that writes a global outside Orca's namespace.

- The daemon dropped an inherited HISTFILE but never an inherited
  ORCA_HISTFILE, which now both wraps a pane the client scoped nothing for and
  re-exports another worktree's history path. The relay had the same gap on its
  isolation-off and revive paths. Both now mirror the desktop.

- A user .zshenv or .zprofile ending in `emulate sh` makes zsh ignore ZDOTDIR,
  so no later wrapper file is read and the epilogue never runs. Nothing can
  repair HISTFILE from there, so the wrapper now detects the emulation and
  hands the pane back unwrapped instead of leaving history somewhere invisible.

Also `typeset -g __orca_in_command` so the OSC 133 preexec hook prints no
warning under `setopt warn_create_global`.
enrick-dev pushed a commit to enrick-dev/orca that referenced this pull request Oct 15, 2026
…Orca panes stop merging worktree histories (STA-4682) (stablyai#15195)

* fix(terminal-history): drop an inherited Orca fish_history (STA-4682)

fish EXPORTS `fish_history`, so an Orca launched from a fish pane keeps the
launching worktree's session name in process.env. Every fish pane of the nested
app then hit the check-before-set early return and wrote into that one
worktree's history file, in every worktree. Drop Orca-minted names (desktop and
relay prefixes) wherever the session is injected, and in the history-disabled
and daemon spawn paths; a genuine user value still wins.

* fix(relay): drop an inherited Orca fish_history on every spawn path (STA-4682)

injectRelayFishHistoryEnv runs only for a fish pane with history isolation
on and a worktreeId, so a relay that inherited an Orca-minted fish_history
kept it on every other path — scoping those panes to another worktree's
history file. The desktop drops it on both branches; scrub it in
buildSpawnEnv so relay spawn and revive match.

Also record why injectWslFishHistoryEnv keeps its own drop (redundant with
both current callers, kept as the function's precondition).
enrick-dev pushed a commit to enrick-dev/orca that referenced this pull request Oct 15, 2026
…ive feature channel (stablyai#15258)

* fix(shell): repair worktree HISTFILE in plain zsh panes via one positive feature channel

A plain zsh pane — no startup command, no agent overlay — was never wrapped, so
Orca's HISTFILE repair never ran in it. macOS `/etc/zshrc` assigns
`HISTFILE=${ZDOTDIR:-$HOME}/.zsh_history` with no check-before-set and runs
before any file Orca controls, so per-worktree history was a silent no-op for
every ordinary pane on the primary platform.

Wrapping those panes needs a way to say which wrapper features a shell should
turn on. That channel is one exported variable, ORCA_SHELL_FEATURES, carrying a
comma-separated positive allowlist from a closed set (history, markers, ready,
identity, overlay). The wrapper .zshenv reads it into a plain, non-exported
array and unsets it in its first executable lines, before the user's own
.zshenv — so the selection survives .zshenv -> .zprofile -> .zshrc -> .zlogin in
this process but physically cannot reach a child. There is no negative or
suppression variable anywhere; an absent or inherited value can only ever mean
fewer features. ORCA_HISTFILE is consumed and destroyed the same way, which
removes the root cause of stablyai#11146 instead of patching it.

All order-sensitive wrapper work now lives in one `__orca_shell_epilogue`
defined in .zshenv and invoked exactly once, from .zshrc for a non-login shell
and .zlogin for a login shell, with each feature an independent guard.

Selection is a pure function of spawn env and launch intent, so a pane wrapped
only for history gets no OSC 133 and is observably identical to the unwrapped
pane it used to be.

Generation is now fail-closed: wrapper files are written to a temp name and
renamed, every required path is verified non-empty, and ZDOTDIR is only set when
that holds. Previously a failed write still pointed ZDOTDIR at an empty dir and
the user silently lost their entire zsh config.

Orca also recognised only its own `*/shell-ready/zsh` dir shape when deciding
what the user's ZDOTDIR was, so being launched from any other terminal that had
hijacked ZDOTDIR captured that as the user's config dir. Ownership is now
established positively — a stamped marker file, or Orca's own dir shape for
wrappers written by older builds — and an inherited ZDOTDIR holding no zsh
startup file is ignored. No vendor is detected by name.

* fix(shell): make the zsh epilogue option-proof and stop history widening relay wrapping

Review follow-ups on the feature-channel PR.

- `emulate -L zsh` as the epilogue's first statement. It runs after the user's
  own config, so `setopt no_unset` made the precmd_functions append a fatal
  error that returned from the whole function (no ready widget, ZDOTDIR left at
  Orca's wrapper dir), and `setopt ksh_arrays` made the 1-based feature
  subscript drop whichever feature is listed first.
- The `/etc/zshrc` HISTFILE repair is no longer behind the `history` guard: it
  undoes damage Orca's own ZDOTDIR caused, so it must also run for a shell that
  re-enters the wrapper after the allowlist was consumed.
- The relay keeps its own wrapping gate. Its .zshenv resolves the user's config
  dir from a ZDOTDIR Orca has already overwritten, so wrapping a remote pane
  just for `history` cost a relocated-ZDOTDIR user their whole shell config.
- A failed primary spawn no longer leaks the primary shell's launch env
  (wrapper ZDOTDIR + feature channel) into an unwrapped fallback pane.

* fix(shell): drop the relay wrapping gate and stop HISTFILE inheriting across Orca instances

The relay-specific gate added last round rested on a false premise:
main's hasOverlayRestoreEnv already included ORCA_REMOTE_CLI_BIN_DIR, and
ssh-pty-spawn-env sets that on every SSH pane whose session has a CLI
bridge — so ordinary remote zsh panes were already wrapped. The gate only
bit where remoteCliBridgeEnv is null (a host too old to report its
platform), where it silently dropped that pane's worktree history. All
three transports now share the features.length rule.

HISTFILE stays exported, so a newly wrapped pane handed the worktree
history path to every child, including a nested Orca whose panes then all
hit injectHistoryEnv's check-before-set and appended into the launching
worktree's file. Same class as the fish_history fix in stablyai#15195: recognise a
path Orca minted and drop it before the check, on the desktop, daemon and
relay injection paths and both history-disabled branches.

Also: run the epilogue from the wrapper .zshrc when zsh is in sh/ksh
emulation, since sourcehome() then reads $HOME/.zlogin and the wrapper's
.zlogin never runs; track fallback launch-env keys per attempt rather than
once from the primary; and guard the cross-file epilogue call so a wrapper
dir shared by two builds degrades quietly.

* fix(shell): make every wrapper file self-sufficient and retire the deleted marker vars from tests

- .zprofile/.zshrc/.zlogin each define __orca_resolve_user_config_dir. They
  called it on line 2 while only .zshenv defined it, so a wrapper dir written by
  two concurrently installed builds printed three "command not found" and
  skipped the user's entire zsh config. New live-shell test covers it.
- Retarget every remaining ORCA_SHELL_READY_MARKER/ORCA_SHELL_STARTUP_IDENTITY
  reference onto ORCA_SHELL_FEATURES, or delete it where the key is now dead.
- isOrcaMintedHistFile requires a leading '/', so a relative path of the same
  shape stays the user's.
- Drop an unused no-control-regex disable, and register the two real-zsh suites
  in the dedicated shell-contracts lane.

* fix(shell): stop the zsh wrapper colliding on REPLY and degrade under sh emulation

Widening wrapping from overlay/startup panes to every zsh pane turned three
latent wrapper defects into user-visible ones.

- The config-dir resolver used `REPLY`, zsh's shared scratch global, as its
  out-parameter. `typeset -r REPLY` in a user config made the wrapper's first
  executable assignment fatal, `typeset -i REPLY` silently resolved every path
  to 0; both left HISTFILE inside Orca's wrapper dir. It now writes an
  Orca-private `_orca_resolved_config_dir`, declared `typeset -g` so the
  rename introduces no `warn_create_global` noise. A new rule test fails on any
  generated wrapper file that writes a global outside Orca's namespace.

- The daemon dropped an inherited HISTFILE but never an inherited
  ORCA_HISTFILE, which now both wraps a pane the client scoped nothing for and
  re-exports another worktree's history path. The relay had the same gap on its
  isolation-off and revive paths. Both now mirror the desktop.

- A user .zshenv or .zprofile ending in `emulate sh` makes zsh ignore ZDOTDIR,
  so no later wrapper file is read and the epilogue never runs. Nothing can
  repair HISTFILE from there, so the wrapper now detects the emulation and
  hands the pane back unwrapped instead of leaving history somewhere invisible.

Also `typeset -g __orca_in_command` so the OSC 133 preexec hook prints no
warning under `setopt warn_create_global`.
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