anthropics / anthropics/claude-agent-sdk-python
discussion: ClaudeAgentOptions.env only supports implicit parent-env inheritance with no documented isolated environment mode
- Dominant language
- Python
- Stars
- 8.1k
- Forks
- 1.3k
- Avg merge
- 2d 31m
- Merged PRs (30d)
- 1
Description
hey, i was exploring `src/claude_agent_sdk/_internal/transport/subprocess_cli.py` to understand how the SDK spawns the Claude Code CLI subprocess and noticed that `connect()` always builds the subprocess environment by inheriting the full parent `os.environ` and then merging user provided `ClaudeAgentOptions.env` on top of it
```python
inherited_env = {k: v for k, v in os.environ.items() if k != "CLAUDECODE"}
process_env = {
**inherited_env,
"CLAUDE_CODE_ENTRYPOINT": "sdk-py",
**self._options.env,
"CLAUDE_AGENT_SDK_VERSION": __version__,
}
```
at a normal usage level this makes sense because `env={"MY_VAR": "value"}` works as an override/addition mechanism, the confusing part is that this inheritance behaviour does not seem to be explicitly documented on `ClaudeAgentOptions.env` and there also does not appear to be a supported way to request a clean or isolated subprocess environment
this looks related to #573 but in this case the concern is broader as users currently get implicit full inheritance but no explicit way to choose between merge with parent env and isolated/clean subprocess env
### Impact
due to this, users cannot create a deterministic or sandboxed subprocess environment and also, sensitive parent variables may be inherited unintentionally and CI/reproducibility use cases become harder
(main point: current behaviour may be intentional but the API contract is not explicit enough to make that clear)
### How to reproduce
Reproduction
```python
import os
import anyio
from claude_agent_sdk import ClaudeAgentOptions
from claude_agent_sdk._errors import CLIConnectionError
from claude_agent_sdk._internal.transport.subprocess_cli import SubprocessCLITransport
class EnvCaptured(Exception):
pass
async def fake_open_process(*args, **kwargs):
env = kwargs["env"]
print("MY_CUSTOM_VAR:", env.get("MY_CUSTOM_VAR"))
print("HOME inherited:", "HOME" in env)
print("PATH inherited:", "PATH" in env)
print("ANTHROPIC_API_KEY inherited:", "ANTHROPIC_API_KEY" in env)
raise EnvCaptured("Stopped after capturing subprocess env")
async def fake_check_claude_version():
return None
async def main():
os.environ["ANTHROPIC_API_KEY"] = "dummy-secret"
options = ClaudeAgentOptions(
env={"MY_CUSTOM_VAR": "hello"},
max_turns=1,
)
transport = SubprocessCLITransport(prompt="hello", options=options)
transport._cli_path = "dummy-claude"
transport._build_command = lambda: ["dummy-claude"]
transport._check_claude_version = fake_check_claude_version
original_open_process = anyio.open_process
anyio.open_process = fake_open_process
try:
await transport.connect()
except CLIConnectionError as e:
if "Stopped after capturing subprocess env" in str(e):
print("Captured env successfully; reproduction complete.")
else:
raise
finally:
anyio.open_process = original_open_process
anyio.run(main)
```
Output:
was the current inheritance-only behaviour intentional or is the lack of an isolated environment mode an oversight? happy to make a pr if this direction sounds useful
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with src/claude_agent_sdk/_internal/transport/subprocess_cli.py, especially SubprocessCLITransport.connect(), and run the reproduction to inspect the environment passed to anyio.open_process. Review related issue #573 and determine whether inheritance is intentional or an isolated mode is needed. Done means the environment contract is explicitly resolved and covered by the appropriate implementation or documentation changes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- cli
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100