Skip to content

fix(claude): restore scrubbed pane identity so CLAUDE_CODE_SUBPROCESS_ENV_SCRUB panes resume - #33286

Merged
nwparker merged 1 commit into
stablyai:mainfrom
nwparker:nwparker/agint-31832-scrub-safe-pane
Oct 24, 2026
Merged

nwparker merged 1 commit into
stablyai:mainfrom
nwparker:nwparker/agint-31832-scrub-safe-pane

Conversation

@nwparker

@nwparker nwparker commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

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 is ORCA_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 of claude --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 2.1.310 (installed binary): CLAUDE_CODE_SUBPROCESS_ENV_SCRUB turns on a name-based scrub (regex built from ["TOKEN","SECRET","PASSWORD",...,"KEY",...], matched on (^|_)NAME(?=$|[_0-9])). ORCA_PANE_KEY, ORCA_AGENT_LAUNCH_TOKEN and ORCA_AGENT_HOOK_TOKEN match it. ORCA_AGENT_PANE, ORCA_AGENT_LAUNCH, ORCA_AGENT_HOOK_ENDPOINT and ORCA_AGENT_HOOK_PORT do not.
  • claude-hook.sh gets the hook token back by sourcing ORCA_AGENT_HOOK_ENDPOINT, but nothing brings back ORCA_PANE_KEY. So the hook stops at [ -z "$ORCA_PANE_KEY" ]. spool_hook_event returns early on the same missing key, so nothing gets spooled either.
  • The aliases are already set on every local/daemon PTY by 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: new buildPosixScrubSafeEnvRestoreLines() and buildWindowsScrubSafeEnvRestoreLines(). 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 remote posix target.
  • src/main/dsh/hook-service.ts: now calls the shared builders. The generated output is byte-identical.
  • One regression test in spool.test.ts. It runs the real generated claude-hook.sh with ORCA_PANE_KEY/ORCA_AGENT_LAUNCH_TOKEN removed 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:

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 with ORCA_PANE_KEY (applyScrubSafeAgentEnvAliases runs after every strip), so a stale alias can't revive an identity Orca removed on purpose. A nested claude -p in a scrubbed pane now sees the same env as on an unscrubbed host, so the #31780 same-provider owner check (ORCA_HOOK_AGENT_PID process capture runs after the restore) still applies.

How verified

Real Claude Code 2.1.310, with Orca's generated claude-hook.sh registered for SessionStart / UserPromptSubmit / Stop, posting to a local capture server. The PTY env includes both canonical and alias names, as Orca sets them:

script scrub hooks received
main =1 0, no spool file. A probe hook shows ORCA_PANE_KEY, ORCA_AGENT_LAUNCH_TOKEN and ORCA_AGENT_HOOK_TOKEN stripped
main =0 3, with paneKey, launchToken and session_id
this PR =1 3, with paneKey=tab-1:leaf-1, launchToken=launch-1, session_id, and the token refreshed from the endpoint file
this PR =0 3 (unchanged)
  • pnpm tc passes. pnpm run check:code-quality:changed passes.
  • 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.ts passes. One flaky unrelated test, claude-transcript-catch-up.test.ts, passes on rerun.
  • Windows (real host, Win 11): generated .cmd posts the restored paneKey/launchToken from aliases only; canonical values win when both are set; nothing posts when neither is set. End to end with real claude.exe -p and the scrub on, the Stop hook posted with the right paneKey, launchToken and session_id.
  • Known tradeoff (scrubbed hosts only): a nested claude -p from 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

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

coderabbitai Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Walkthrough

Shared POSIX and Windows builders restore canonical pane identity variables from scrub-safe aliases. The Claude and dsh managed scripts use these builders. A new test checks that a Claude hook spools a record with the pane key and launch token when the canonical variables are absent.

Priority: Normal

Severity of issue fixed: Medium

Merge Risk: Moderate · up to 3974d

The new test can fail the Windows preflight before checking Claude hook behavior. Guard the POSIX-only test before merging.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 4 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title clearly identifies the main change: restoring scrubbed Claude pane identity so scrub-enabled panes can resume.
Linked Issues check Passed Issue #31832 requires that Claude panes resume with claude --resume <session-id> when CLAUDE_CODE_SUBPROCESS_ENV_SCRUB removes ORCA_PANE_KEY. Code at head meets this for the Claude hook. `src/ma…
Out of Scope Changes check Passed All changed files connect to the Claude pane-identity restore for #31832. src/shared/agent-hook-scrub-safe-env.ts adds the shared POSIX and Windows restore builders and updates the scrub documentati…
Description check Passed The description is thorough and follows the template closely. It has ELI5, What Changed, Why (root cause and alternatives, including the decision to leave out Codex, Grok, and Antigravity), Linked Iss…
Full details: Linked Issues check

Explanation

Issue #31832 requires that Claude panes resume with claude --resume &lt;session-id&gt; when CLAUDE_CODE_SUBPROCESS_ENV_SCRUB removes ORCA_PANE_KEY. Code at head meets this for the Claude hook. src/main/claude/hook-script.ts now emits buildPosixScrubSafeEnvRestoreLines() after the printf "{}\n" line and before payload capture, guards, and spool logic. The Windows script emits buildWindowsScrubSafeEnvRestoreLines() before the endpoint refresh and guards. The restore lines in src/shared/agent-hook-scrub-safe-env.ts use := and if not defined, so live values are not overwritten. The POSIX endpoint refresh and ORCA_AGENT_HOOK_TOKEN guard still run after the restore, so the token recovery path is preserved. spool.test.ts adds a test that runs the generated POSIX Claude script with ORCA_PANE_KEY and ORCA_AGENT_LAUNCH_TOKEN removed and checks that the spooled record carries paneKey and launchToken. The author reports real Claude Code 2.1.310 runs (0 hooks on main with scrub on, 3 hooks with the change). I did not reproduce those runs. The issue also names the Codex, Grok, and Antigravity hook scripts. The issue's Expected behavior and title are Claude-specific. The PR states that those agents do not scrub and does not change them. I did not inspect those external repositories, so that scope judgment is not independently verified here. It does not block this assessment. Windows output is not executed. The Windows restore uses the same if not defined pattern as the existing DSH script.

Full details: Out of Scope Changes check

Explanation

All changed files connect to the Claude pane-identity restore for #31832. src/shared/agent-hook-scrub-safe-env.ts adds the shared POSIX and Windows restore builders and updates the scrub documentation. src/main/claude/hook-script.ts wires the builders into the Claude managed script. src/main/dsh/hook-service.ts replaces its inline restore with the same shared builders. This is a refactor that removes duplicate logic. It is not a separate behavior change, and the author reports byte-identical DSH output. I did not diff the DSH output to confirm this. src/main/agent-hooks/spool.test.ts adds a supporting regression test for the new POSIX restore. No change adds Codex, Grok, Antigravity, or SSH-remote behavior. The PR description states these exclusions.

Full details: Description check

Explanation

The description is thorough and follows the template closely. It has ELI5, What Changed, Why (root cause and alternatives, including the decision to leave out Codex, Grok, and Antigravity), Linked Issue (Fixes #31832), Testing with a before/after table and platforms covered (macOS/Linux via real Claude Code 2.1.310, Windows on a real host), a Risk section, and a known remaining gap for SSH-remote PTYs. The Visual Proof section is missing, but the change has no UI impact, so it could be marked N/A and the reason stated. The Review/Agent skill upstream boundary, AI Disclosure, and Checklist sections are not filled in. The Testing checkboxes are not ticked. Those are minor omissions compared with the rest of the description.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

❤️ Share

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

@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
  • 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
Commits

Reviewing files that changed from the base of the PR and between 08ec2b3 and 3974da7.

Files selected for processing: 4
  • src/main/agent-hooks/spool.test.ts
  • src/main/claude/hook-script.ts
  • src/main/dsh/hook-service.ts
  • src/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], {

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.

Major · Stability & Availability · Quick win

🔎 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', () => {

@nwparker
nwparker merged commit 2aae457 into stablyai:main Oct 24, 2026
40 checks passed
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.

[Bug]: A harness that scrubs KEY/TOKEN-named env drops ORCA_PANE_KEY, so Claude hooks are silently lost and panes never resume after a restart

1 participant