[FEATURE] CLAUDE_ENV_FILE should propagate to PowerShell tool and child plugins, not bash-only
What's the problem
CLAUDE_ENV_FILE (populated by SessionStart / Setup / CwdChanged / FileChanged hooks) is documented as making env vars \"available in all subsequent Bash commands that Claude Code executes during the session\" — and that's exactly what it does. The values land in bash tool subprocesses, but nothing else sees them.
On Windows with PowerShell as the daily driver, this creates a sharp split: bash tool calls have my OAuth / API tokens, while the PowerShell tool, the statusline command, MCP servers, and any external plugin Claude Code spawns directly all see $null for those same vars. Subagents have the same problem (already filed as #46696).
Why it matters
We use a SessionStart hook to resolve 1Password references in .claude/secrets.env via op inject and write the resulting export KEY='value' lines to $CLAUDE_ENV_FILE. This is exactly the workflow the docs describe.
Result today, on Windows + pwsh:
- Bash tool:
JIRA_TOKEN,OCTOPUS_API_KEY, etc. all populated ✅ - PowerShell tool: all
$null❌ - Statusline (pwsh, configured in
.claude/settings.json): can't see the loaded vars, so it falsely shows🔒 0/3even though everything is loaded - Any pwsh-based plugin / MCP server: same — can't see them
- 3rd-party plugins: not under our control, so no \"just wrap it\" workaround
We can patch our own statusline plugin to fall back to parsing ~/.claude/session-env/<sid>/sessionstart-hook-*.sh to recover a truthful indicator, but that's a band-aid for one consumer. The PowerShell tool, MCP servers, and external plugins remain blind. There is no user-land fix that covers all of them.
What I'd like
Make CLAUDE_ENV_FILE semantics tool/shell-agnostic: when a hook appends KEY=value (or export KEY='value') lines to $CLAUDE_ENV_FILE, Claude Code should set those env vars on its own process so that every child process — bash tool, PowerShell tool, MCP servers, plugins, subagents, statusline command — inherits them naturally via OS-level env propagation.
That removes a whole class of \"why doesn't X see my secrets\" problems and also subsumes #46696 (subagent inheritance) and arguably #51862 (bash-tool sourcing), since with vars on the parent process there's no need to source anything per-child.
Acceptance suggestions
- Parser accepts both
KEY=valueandexport KEY='value'(the latter is what bash-style hook examples in your own docs produce). - Single-quote escaping (
'\'') honored, sinceop injectoutput uses that. $env:CLAUDE_ENV_FILEitself still set inside hook subprocesses so existing hooks continue to work unchanged.- Optional: surface the resolved env via
/env(which would also close part of #41268).
Workarounds considered and rejected
- Wrapper script for pwsh tools — works for the statusline (one entry point we control), but doesn't reach MCP servers, external plugins, or future tooling that Claude Code spawns directly. Wrapping isn't viable as a general answer.
- Persist secrets to Windows User env via
[Environment]::SetEnvironmentVariable($k, $v, 'User')— works mechanically but writes rotating 1Password tokens to the registry, persists past the session, and survives Claude Code exits. Security-smell. - Always launch Claude Code from a pre-populated shell (e.g.
op run -- pwshthenclaude) — works perfectly when remembered, but the SessionStart hook exists precisely because plainclaudelaunches need to recover. Today the recovery is bash-only.
Environment
- Claude Code: latest stable, Windows 11
- Shell: PowerShell 7
- Hook:
SessionStartwritingexport KEY='value'lines to$CLAUDE_ENV_FILE(the exact format your hooks docs show)
Filed via Claude Code on my own behalf — meta enough that I had to flag it 😄
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗