Skip to content

Provider-home env vars are neutralized to "" instead of unset, so the Claude CLI falls back to cwd and writes session state into the agent worktree #986

Description

@mrthankyou

Summary

workflowControlChildEnvironment neutralizes BUILT_IN_PROVIDER_HOME_ENVIRONMENT_VARIABLES (including CLAUDE_CONFIG_DIR) by assigning the empty string:

const child: Record<string, string> = Object.fromEntries(
  [
    ...CONTROLLER_ONLY_ENVIRONMENT_VARIABLES,
    ...BUILT_IN_PROVIDER_HOME_ENVIRONMENT_VARIABLES,
    ...providerCredentialEnvironmentVariables(source)
  ].map((name) => [name, ""])
);

An empty value is not equivalent to an unset variable. Claude Code treats an empty CLAUDE_CONFIG_DIR as absent and falls back to the current working directory for its config dir, rather than to ~/.claude.

Impact

When an agent runs without a forwarded config directory, the CLI writes its session state into the agent's own git worktree:

projects/<munged-cwd>/<session-id>.jsonl
projects/<munged-cwd>/<session-id>/tool-results/<id>.txt

Those files become part of the workspace patch. They are agent session transcripts, so this also means conversation state is written into a tree that is diffed, snapshotted, and handed to downstream nodes.

Combined with the 128-character WORKSPACE_PATCH_SEGMENT cap (filed separately), <munged-cwd> is the absolute worktree path with separators replaced, so the run fails non-deterministically depending on path length:

✗ verify:setup-foundry (attempt 1): workspace patch file path contains an unsafe path segment
✗ Run failed: Task failed: verify:setup-foundry

Two runs of the same project, differing only in the length of the run id, produced a 123-character segment (passed) and a 130-character segment (failed).

Suggested direction

Neutralizing by assignment is the right intent, but for variables whose absence is meaningful the child environment should omit the key rather than set it to "". Worth auditing the other entries in BUILT_IN_PROVIDER_HOME_ENVIRONMENT_VARIABLES (CODEX_HOME, KIMI_CODE_HOME, KIMI_SHARE_DIR) for the same CLI-dependent behavior.

I have not attempted a fix: this is the credential/environment isolation boundary and I could not validate a change to it across all providers.

Notes

  • Reproduced on darwin/arm64 with ClaudeAgent + auth = "subscription".
  • Not macOS-specific in principle — the empty-vs-unset distinction applies anywhere.
  • Encountered while making the config directory conditional; the correct resolution there was operational (register the config dir once via CLAUDE_CONFIG_DIR=<dir> claude + /login, since Claude Code scopes the subscription credential per config directory). But the empty-string neutralization remains a latent trap for any code path that legitimately omits a provider home.

🤖 Generated with Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions