Skip to content

fix(agent-hooks): restore scrubbed pane identity in the claude, codex, grok and antigravity hooks - #31834

Closed
daviddesmet wants to merge 1 commit into
stablyai:mainfrom
daviddesmet:fix/scrub-safe-pane-identity-in-managed-hooks
Closed

daviddesmet wants to merge 1 commit into
stablyai:mainfrom
daviddesmet:fix/scrub-safe-pane-identity-in-managed-hooks

Conversation

@daviddesmet

@daviddesmet daviddesmet commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

ELI5

Some agent harnesses refuse to hand a hook any environment variable whose name looks like a password; anything with KEY, TOKEN, SECRET or PASSWORD in it. One of the things Orca puts in there is the name of the pane the hook is reporting for. When that gets taken away, the hook has no idea which pane it belongs to, so it gives up and says nothing. Orca then never learns the agent's session id, and when you quit and reopen the app your panes come back empty instead of picking the conversation back up.

Orca already keeps a spare copy of that pane name under a safe-looking name for exactly this reason, but only the DSH hook knew to look for it. This teaches the Claude, Codex, Grok and Antigravity hooks to look for it too.

What Changed

Before: with a scrubbing harness, agent panes restore to a bare shell after an app restart.
Status badges still look right the whole time, so nothing points at the hooks.

After: the same panes restore with claude --resume <session-id> again, with the harness's credential scrub left switched on.

Mechanism: every managed POSIX hook ends in this guard, and a scrubbed ORCA_PANE_KEY trips it:

if [ -z "$ORCA_AGENT_HOOK_PORT" ] || [ -z "$ORCA_AGENT_HOOK_TOKEN" ] || [ -z "$ORCA_PANE_KEY" ]; then
  spool_hook_event
  exit 0
fi

ORCA_AGENT_HOOK_TOKEN recovers on its own; the line above sources ORCA_AGENT_HOOK_ENDPOINT and gets the live port/token back. ORCA_PANE_KEY has no recovery path, and the spool fallback cannot catch the event either, because spool_hook_event returns early on the same missing pane key. The event is dropped with no file, no log line and no retry.

That silence is what makes the bug hard to place: OSC 9999 keeps the pane's status badges accurate, so only the hook-derived facts go missing; providerSession never lands on the status row, no sleeping record is filed, buildColdRestoreAgentResumeStartup() finds neither a live row nor a sleeping record and returns null, and the replacement spawn gets no startup command (daemon.log: "event":"startup-command-delivery" ... "hasCommand":false).

This PR lifts the existing dsh restore into two shared builders next to the alias definitions in agent-hook-scrub-safe-env.ts; buildPosixScrubSafeEnvRestoreLines and buildWindowsScrubSafeEnvRestoreLines, and uses them in the claude, codex, grok and antigravity managed scripts on both POSIX and Windows. dsh now calls the shared builders and emits byte-identical output.

In the Claude script the restore sits immediately after the neutral {} that permission hooks need on stdout (#14818), and before anything that reads pane identity.

Why

Orca already solved this for DSH: applyScrubSafeAgentEnvAliases() mirrors ORCA_PANE_KEY and ORCA_AGENT_LAUNCH_TOKEN onto ORCA_AGENT_PANE / ORCA_AGENT_LAUNCH at spawn, and the DSH managed script restores the canonical names from them. The aliases are already being set for every pane; only the consuming half was missing from the other four hooks. Reusing that machinery beats the alternatives:

  • Rename the env vars. Breaks every existing agent, script and test that reads them, and the aliases exist precisely so the canonical names can stay.
  • Fix it per-script. Four near-identical copies of the same three lines, which is how the DSH version came to be the only one.
  • Pass the pane key some other way (argv, the endpoint file). A bigger wire change for a problem a one-line default-assignment already solves, and the endpoint file is shared by all panes so it cannot carry per-pane identity.

The restore is default-assignment (: "${ORCA_PANE_KEY:=${ORCA_AGENT_PANE:-}}"), never an overwrite, so an unscrubbed host keeps the value it already had and every line below is unchanged.

This is not DSH-specific any more. Claude Code performs the same scrub when CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1 is set in its settings, which is how the linked issue was found.

Linked Issue

Fixes #31832

Visual Proof

N/A — no UI or interaction surface changes. The change is confined to the text of generated hook scripts; the user-visible difference is that a restored pane runs its resume command again, which the Testing section covers.

Testing

Platforms actually tested: macOS 26.0 (arm64), local worktrees. Windows and Linux changes are covered by the generated-script tests below, not by hand.

Reproduction and verification, with Claude Code 2.1.307 and "env": { "CLAUDE_CODE_SUBPROCESS_ENV_SCRUB": "1" }:

  1. Dumped env from a hook subprocess. Stripped: ORCA_PANE_KEY, ORCA_AGENT_HOOK_TOKEN,
    ORCA_AGENT_LAUNCH_TOKEN. Surviving: ORCA_AGENT_PANE, ORCA_TAB_ID, ORCA_WORKTREE_ID,
    ORCA_AGENT_HOOK_ENDPOINT / _PORT / _ENV / _VERSION / _TRANSPORT.
  2. Isolated the layer: running claude-hook.sh by hand with the full env produced a complete status row with providerSession, so ingest was never the problem. A real claude -p run under the same env delivered nothing and created no spool file. A touch hook in the same Stop slot did fire, so hooks were running; Orca's specifically was not.
  3. Applied the restore. The same claude -p run now lands a row with both providerSession and launchTokenHash.
  4. Restarted Orca with three live Claude panes: all three resumed, where before the change all three came back at a bare shell.
  5. Confirmed the inverse: with the scrub set to 0, the pane key arrives and stock Orca works; consistent with this only reproducing on machines that opt into the scrub.

Checks run locally on this branch:

  • pnpm tc: no type errors across all four projects (17,438 / 3,097 / 34 / 15,521 files)

  • pnpm test src/main/claude src/main/codex src/main/grok src/main/antigravity src/main/dsh src/main/agent-hooks src/shared/agent-hook: 697 files, 7,003 tests passing

  • oxlint clean on every changed file

  • pnpm build not run locally; leaving that to CI

  • I manually tested these changes locally

  • Automated tests added/updated, or explained why not below

Added src/main/agent-hooks/managed-hook-scrub-safe-restore.test.ts: for all four POSIX managed scripts it asserts the restore lines are present and ordered before both the missing-pane-key guard and the spool fallback that reads the same variable. Ordering is the part that actually regresses; a restore placed after either one is still dead code; so the test pins the order rather than just the presence of the lines.

AI Disclosure

Review

  • Cross-platform: POSIX and Windows branches both updated for all four agents, including the Antigravity .cmd wrapper and the Windows Grok script. The Windows form is the same if not defined idiom already shipping in the DSH script. No path, shell or platform assumptions added.
  • SSH / remote / local: the restore is emitted into the managed script itself, so it travels with the script to remote and SSH installs. The posix target used for remote installs is the same code path the tests cover.
  • Agent / integration compatibility: no change for any agent on a host that does not scrub; := only fills an empty value, so existing env wins and output is unchanged. dsh output is byte-identical to before this PR. Agents without a managed script are untouched.
  • Backwards compatibility: no wire-format, settings or state-schema change. Hook scripts are regenerated on install, and an older script keeps working; it just keeps the old failure mode under a scrubbing harness.
  • Performance: two shell parameter expansions per hook invocation, no subprocess and no I/O.
  • Security: this restores Orca's own pane identity from a value Orca itself set on the same process; it does not defeat the harness's credential scrub, re-expose any scrubbed secret, or widen what the hook can read. ORCA_AGENT_HOOK_TOKEN is deliberately left to the existing endpoint-file path rather than aliased.
  • UI quality: not applicable, no UI surface touched.

Agent skill upstream boundary

  • Not applicable, or this change copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

The bug reproduces only on machines that opt into a scrubbing harness, which is why it looks machine-specific: the same Orca version resumes fine on a second Mac without CLAUDE_CODE_SUBPROCESS_ENV_SCRUB. Worth knowing when triaging similar "resume stopped working on one machine" reports.

Author: X — @daviddsmet

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • 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)

…, grok and antigravity hooks

A harness that drops env vars whose name contains KEY, TOKEN, SECRET or
PASSWORD takes ORCA_PANE_KEY with it, and the managed hook then exits at its
own guard without posting. ORCA_AGENT_HOOK_TOKEN recovers by sourcing the
endpoint file; ORCA_PANE_KEY has no recovery, and the spool fallback returns
early on the same missing key, so the event is lost with no trace.

Without the hook Orca never sees the provider session id, so no sleeping
record is filed and cold restore has nothing to resume — the pane comes back
at a bare shell.

Lift the dsh restore into shared builders beside the alias definitions and use
them in the four remaining managed scripts, POSIX and Windows. dsh now calls
the shared builders and emits byte-identical output.
@coderabbitai

coderabbitai Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 69787333-d029-44c2-bf66-17fbcd3106b7
📥 Commits

Reviewing files that changed from the base of the PR and between 0a7d6bb and 018ec3c.

📒 Files selected for processing: 8
  • src/main/agent-hooks/managed-hook-scrub-safe-restore.test.ts
  • src/main/antigravity/hook-script.ts
  • src/main/claude/hook-script.ts
  • src/main/codex/codex-hook-script.ts
  • src/main/dsh/hook-service.ts
  • src/main/grok/grok-hook-script.ts
  • src/main/grok/windows-grok-hook-script.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; 9 remain after this review.

📝 Walkthrough

Shared POSIX and Windows builders now restore canonical identity variables from aliases when those canonical variables are unset. Claude, Codex, Grok, and Antigravity managed hook scripts emit the restoration lines before later hook processing. The DSH hook service now uses the shared builders. Tests verify restoration order in four POSIX managed hook scripts.

Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 018ec

The reviewed restoration and fallback paths support pane-session recovery. No identified issue blocks merge after normal checks.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 10 functions across 8 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 and concisely describes the primary change: restoring scrubbed pane identity in the Claude, Codex, Grok, and Antigravity hooks.
Linked Issues check Passed Direct issue [#31832] requires Claude hooks to retain pane identity when a harness removes ORCA_PANE_KEY, so hook events can provide the session ID and cold restore can run claude --resume. The sh…
Out of Scope Changes check Passed The changes stay within [#31832]. Shared restore builders, DSH reuse, managed-script updates for the four affected agents, and generated-script tests directly support scrub-safe pane identity and sess…
Description check Passed The pull request description is complete and follows the repository template. It includes the ELI5 summary, user-visible changes, mechanism, rationale, linked issue, visual proof, testing details, rev…
Full details: Linked Issues check

Explanation

Direct issue [#31832] requires Claude hooks to retain pane identity when a harness removes ORCA_PANE_KEY, so hook events can provide the session ID and cold restore can run claude --resume. The shared POSIX and Windows builders restore ORCA_PANE_KEY and ORCA_AGENT_LAUNCH_TOKEN from scrub-safe aliases without overwriting existing values. The builders are used by Claude, Codex, Grok, and Antigravity managed scripts. The Claude restore follows the permission-hook {} output and precedes payload, guard, and spool processing. Tests cover all four POSIX scripts and verify restore ordering before the missing-pane-key guard and spool fallback. The reported implementation and test evidence satisfy the linked coding requirements.

Full details: Out of Scope Changes check

Explanation

The changes stay within [#31832]. Shared restore builders, DSH reuse, managed-script updates for the four affected agents, and generated-script tests directly support scrub-safe pane identity and session restoration. The source documentation explains the scrub behavior and alias contract. No unrelated product behavior, state schema, wire format, or UI change is shown.

Full details: Description check

Explanation

The pull request description is complete and follows the repository template. It includes the ELI5 summary, user-visible changes, mechanism, rationale, linked issue, visual proof, testing details, review notes, platform and security considerations, upstream boundary, and checklist. It clearly states that pnpm build was not run locally and is left to CI. The AI Disclosure section is appropriately left blank for an internal contributor.

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

nwparker added a commit that referenced this pull request Oct 24, 2026
#33286)

Claude's hook script refills ORCA_PANE_KEY / ORCA_AGENT_LAUNCH_TOKEN from the scrub-safe aliases when CLAUDE_CODE_SUBPROCESS_ENV_SCRUB strips them, so scrubbed Claude panes report status and resume again. Fixes #31832. Supersedes #31834.

Co-authored-by: David De Smet <2607383+daviddesmet@users.noreply.github.com>
@nwparker

Copy link
Copy Markdown
Contributor

Thanks @daviddesmet — you found this one and the alias-restore approach. It landed via #33286 (credited with Co-authored-by). We kept the restore Claude-only: Codex and Grok hooks don't scrub the env (codex-rs/hooks/src/engine/command_runner.rs, xai-grok-hooks/src/runner/command.rs), and restoring there would let a nested codex inside a scrubbed Claude pane claim the parent pane. Closing as superseded.

@nwparker nwparker closed this Oct 24, 2026
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

2 participants