Repository navigation
fix(claude): restore scrubbed pane identity so CLAUDE_CODE_SUBPROCESS_ENV_SCRUB panes resume - #33286
Conversation
Claude Code's CLAUDE_CODE_SUBPROCESS_ENV_SCRUB drops ORCA_PANE_KEY and ORCA_AGENT_LAUNCH_TOKEN from hook processes, so claude-hook.sh exited on its pane-key guard without posting or spooling and panes never got a resumable session id. Reuse the DSH alias restore (ORCA_AGENT_PANE / ORCA_AGENT_LAUNCH) via shared builders. Fixes stablyai#31832 Co-authored-by: David De Smet <2607383+daviddesmet@users.noreply.github.com>
Walkthrough
Priority: Severity of issue fixed: Medium Merge Risk:
|
There was a problem hiding this comment.
Actionable comments posted: 1

Review info
-
Run configuration-
- Source mode: Auto
- Repository: Unavailable
- Branch: Unavailable
- File: No configuration file from this repository applied
- Commit: Unavailable
- Layers: Repository UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
44463cd2-8134-4faa-bc35-8f44d54bfd59
Files selected for processing: 4-
src/main/agent-hooks/spool.test.tssrc/main/claude/hook-script.tssrc/main/dsh/hook-service.tssrc/shared/agent-hook-scrub-safe-env.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 0 remain after this review.
-
| delete env.ORCA_PANE_KEY | ||
| delete env.ORCA_AGENT_LAUNCH_TOKEN | ||
| delete env.CLAUDE_JOB_DIR | ||
| execFileSync('/bin/sh', [script], { |
There was a problem hiding this comment.
🔎 Supported by static analysis
-
Script executed:
sed -n '1,310p' src/main/agent-hooks/spool.test.ts sed -n '1,100p' config/scripts/ci-unit-files.mjs sed -n '45,95p' config/vitest.config.ts
Repository: stablyai/orca
Length of output: 16640
Script executed:
set -eu printf '%s\n' '--- package scripts containing test/unit/vitest ---' python3 - <<'PY' import json from pathlib import Path p = Path('package.json') data = json.loads(p.read_text()) for k, v in data.get('scripts', {}).items(): if any(term in k.lower() or term in str(v).lower() for term in ('test', 'vitest', 'unit')): print(f'{k}: {v}') PY printf '%s\n' '--- workflow references to Windows and unit/Vitest commands ---' rg -n -i -F --glob '.github/workflows/*' -e 'windows' -e 'vitest' -e 'pnpm test' -e 'ci-unit-files' -e 'unit' .github/workflows || test "$?" -eq 1 printf '%s\n' '--- all direct references to this test or its enclosing test command ---' rg -n -F --glob '*.yml' --glob '*.yaml' --glob '*.json' --glob '*.mjs' --glob '*.ts' -e 'spool.test.ts' -e 'run test' -e 'test:unit' -e 'vitest run' . || test "$?" -eq 1
Repository: stablyai/orca
Length of output: 41116
Skip this POSIX-only test on native Windows.
The Windows preflight matrix runs the full Node Vitest configuration. This test has no enclosing or platform-specific exclusion. Without /bin/sh, execFileSync('/bin/sh', [script], ...) can fail before the assertions run.
🐛 Suggested fix
-
- it('keeps a Claude hook attributed when CLAUDE_CODE_SUBPROCESS_ENV_SCRUB drops the pane key', () => { + it.skipIf(process.platform === 'win32')('keeps a Claude hook attributed when CLAUDE_CODE_SUBPROCESS_ENV_SCRUB drops the pane key', () => {
ELI5
When you turn on Claude Code's
CLAUDE_CODE_SUBPROCESS_ENV_SCRUB, Claude deletes every environment variable whose name looks like a secret (*KEY*,*TOKEN*, ...) before it runs a hook. One of those isORCA_PANE_KEY, which is how Orca's hook says which pane it belongs to. Without it the hook quietly does nothing. Orca never learns the Claude session id, so after a restart the pane comes back as a plain shell instead ofclaude --resume <id>.Orca already sets a backup copy under a name the scrub leaves alone (
ORCA_AGENT_PANE/ORCA_AGENT_LAUNCH), and the DSH hook already reads it. This PR makes the Claude hook read it too.Root cause
CLAUDE_CODE_SUBPROCESS_ENV_SCRUBturns on a name-based scrub (regex built from["TOKEN","SECRET","PASSWORD",...,"KEY",...], matched on(^|_)NAME(?=$|[_0-9])).ORCA_PANE_KEY,ORCA_AGENT_LAUNCH_TOKENandORCA_AGENT_HOOK_TOKENmatch it.ORCA_AGENT_PANE,ORCA_AGENT_LAUNCH,ORCA_AGENT_HOOK_ENDPOINTandORCA_AGENT_HOOK_PORTdo not.claude-hook.shgets the hook token back by sourcingORCA_AGENT_HOOK_ENDPOINT, but nothing brings backORCA_PANE_KEY. So the hook stops at[ -z "$ORCA_PANE_KEY" ].spool_hook_eventreturns early on the same missing key, so nothing gets spooled either.applyScrubSafeAgentEnvAliases(src/shared/agent-hook-scrub-safe-env.ts). Only the DSH script had the code that reads them back.What changed
src/shared/agent-hook-scrub-safe-env.ts: newbuildPosixScrubSafeEnvRestoreLines()andbuildWindowsScrubSafeEnvRestoreLines(). They emit the same lines the DSH script already hardcoded. These are default assignments (:=/if not defined), so a live value is never overwritten.src/main/claude/hook-script.ts: the restore now runs right after the permission-hook{}line ([Bug]: Windows shell command failures #14818) and before anything reads pane identity, on both POSIX and Windows. This also covers the remoteposixtarget.src/main/dsh/hook-service.ts: now calls the shared builders. The generated output is byte-identical.spool.test.ts. It runs the real generatedclaude-hook.shwithORCA_PANE_KEY/ORCA_AGENT_LAUNCH_TOKENremoved and only the aliases present, then checks that the event lands attributed to the pane. It fails on main and passes here.Relation to #31834 (credit: David De Smet)
This is a narrower version of #31834. That PR found the bug and the alias-restore approach, and is credited via
Co-authored-by. I left out the Codex, Grok and Antigravity parts on purpose:codex-rs/hooks/src/engine/command_runner.rsbuild_commandinherits the full env plushandler.env, with noenv_clear.grok-build/crates/codegen/xai-grok-hooks/src/runner/command.rs:178,220only addsenvs. Also, Orca's Grok hook command is gated byrequiredEnvVar: 'ORCA_PANE_KEY'(grok-hook-script.ts:26). So a scrubbed Grok would never reach a restore inside the script, which would make it dead code.ORCA_AGENT_PANEand losesORCA_PANE_KEY. Today acodexrun inside a scrubbed Claude pane can't claim that pane. With fix(agent-hooks): restore scrubbed pane identity in the claude, codex, grok and antigravity hooks #31834's Codex restore it could. Keeping the restore to the Claude hook only brings the scrubbed case back to what an unscrubbed host already does.Compatibility with related open PRs: no file overlap with #31205, #26426, #28267, #28288 or #30930. #31205 forwards the same aliases through
WSLENV, which this restore uses. Structured children (STRUCTURED_CHILD_ENV_TO_DELETE) and the PTY spawn paths drop or re-derive the aliases together withORCA_PANE_KEY(applyScrubSafeAgentEnvAliasesruns after every strip), so a stale alias can't revive an identity Orca removed on purpose. A nestedclaude -pin a scrubbed pane now sees the same env as on an unscrubbed host, so the #31780 same-provider owner check (ORCA_HOOK_AGENT_PIDprocess capture runs after the restore) still applies.How verified
Real Claude Code 2.1.310, with Orca's generated
claude-hook.shregistered for SessionStart / UserPromptSubmit / Stop, posting to a local capture server. The PTY env includes both canonical and alias names, as Orca sets them:=1ORCA_PANE_KEY,ORCA_AGENT_LAUNCH_TOKENandORCA_AGENT_HOOK_TOKENstripped=0paneKey,launchTokenandsession_id=1paneKey=tab-1:leaf-1,launchToken=launch-1,session_id, and the token refreshed from the endpoint file=0pnpm tcpasses.pnpm run check:code-quality:changedpasses.pnpm test src/main/agent-hooks src/main/claude src/main/dsh src/main/devin src/main/cursor src/main/grok src/shared/agent-hook-scrub-safe-env.test.tspasses. One flaky unrelated test,claude-transcript-catch-up.test.ts, passes on rerun..cmdposts the restored paneKey/launchToken from aliases only; canonical values win when both are set; nothing posts when neither is set. End to end with realclaude.exe -pand the scrub on, the Stop hook posted with the right paneKey, launchToken and session_id.claude -pfrom the parent's Bash tool now gets its pane key back, just like on an unscrubbed host. On POSIX, fix(agent-status): a nested claude -p no longer takes over its parent pane's status #31780's owner check still rejects it. Windows hooks carry no process identity, so there it behaves like today's unscrubbed Windows baseline.Risk
Low. The hook prologue only fills empty values from aliases that Orca itself sets on the pane PTY. Hosts without the scrub are unchanged. Already-installed scripts get the fix when Orca refreshes its managed hook scripts.
Remaining gap, not addressed here: SSH-remote PTYs don't set the aliases today, so a scrubbed Claude on a remote host still won't be attributed. DSH has the same gap.
Fixes #31832