You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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/3 even 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=value and export KEY='value' (the latter is what bash-style hook examples in your own docs produce).
Single-quote escaping ('\'') honored, since op inject output uses that.
$env:CLAUDE_ENV_FILE itself still set inside hook subprocesses so existing hooks continue to work unchanged.
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 -- pwsh then claude) — works perfectly when remembered, but the SessionStart hook exists precisely because plain claude launches need to recover. Today the recovery is bash-only.
Environment
Claude Code: latest stable, Windows 11
Shell: PowerShell 7
Hook: SessionStart writing export 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 😄
What's the problem
CLAUDE_ENV_FILE(populated bySessionStart/Setup/CwdChanged/FileChangedhooks) 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
$nullfor 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.envviaop injectand write the resultingexport KEY='value'lines to$CLAUDE_ENV_FILE. This is exactly the workflow the docs describe.Result today, on Windows + pwsh:
JIRA_TOKEN,OCTOPUS_API_KEY, etc. all populated ✅$null❌.claude/settings.json): can't see the loaded vars, so it falsely shows🔒 0/3even though everything is loadedWe can patch our own statusline plugin to fall back to parsing
~/.claude/session-env/<sid>/sessionstart-hook-*.shto 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_FILEsemantics tool/shell-agnostic: when a hook appendsKEY=value(orexport 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
KEY=valueandexport KEY='value'(the latter is what bash-style hook examples in your own docs produce).'\'') honored, sinceop injectoutput uses that.$env:CLAUDE_ENV_FILEitself still set inside hook subprocesses so existing hooks continue to work unchanged./env(which would also close part of [DOCS] Built-in commands reference missing/envcoverage for PowerShell tool #41268).Workarounds considered and rejected
[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.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
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 😄