Skip to content

fix(process_env): mirror carrier writes into the real env on Windows - #11

Open
OriyukiKiri wants to merge 3 commits into
Haleclipse:masterfrom
OriyukiKiri:fix/windows-process-env-write-through
Open

OriyukiKiri wants to merge 3 commits into
Haleclipse:masterfrom
OriyukiKiri:fix/windows-process-env-write-through

Conversation

@OriyukiKiri

@OriyukiKiri OriyukiKiri commented Sep 22, 2026

Copy link
Copy Markdown

Summary

On Windows the real-environment write-through in EnvUpdate::commit was gated behind #[cfg(unix)],
so the loop was compiled out of Windows builds entirely: the carrier was updated and std::env was
not. settings.json envANTHROPIC_AUTH_TOKEN included — therefore stayed invisible to the 516
raw std::env readers in the tree, and auth never succeeded.

The fix widens the eight #[cfg(unix)] gates that carry the write-through to
#[cfg(any(unix, windows))]:

  • operation-log plumbing: the os_operations field, begin_update's initializer, enum OsOperation
  • queue sites: EnvUpdate::set, EnvUpdate::remove, both restore_entry branches
  • publication: the write-through loop in commit

The operation log was already platform-ready (truncate_at_nul / contains_equals / keys_equal /
array_index all carry #[cfg(windows)] arms), and Windows set_var is documented sound — the same
premise entrypoints/cli.rs:19-23 already relies on. The Unix arm is unchanged.

The Windows test that codified the defect as intent is inverted:
windows_carrier_operations_leave_real_environment_unchanged
windows_carrier_operations_mirror_into_real_environment.

Reproduction

Strip the credential the host process already exports, so it can only come from a settings file:

env -u ANTHROPIC_AUTH_TOKEN -u ANTHROPIC_API_KEY ./target/debug/cometix.exe -p "hi" --no-session-persistence

Before: both the user source (CLAUDE_CONFIG_DIR) and the flag source (--settings) failed with the
SDK's Could not resolve authentication method.

After:

  • ANTHROPIC_CUSTOM_HEADERS goes from present: false to present: true
  • ANTHROPIC_BASE_URL from settings takes effect (connects to 19999, not the inherited 18936)
  • the --settings file completes a real round trip

(apiKeyHelper reads settings only, never env — utils/auth.rs:187 — and it did take effect: proof
the file was being read, the value just could not be written back.)

Tests

  • windows_carrier_operations_mirror_into_real_environment (new: the write lands, removal clears both
    sides, names normalization rejects land on neither)
  • cargo test --lib process_env:: — 14 passed / 0 failed

Summary by Sourcery

Mirror carrier-managed environment updates into the real process environment on Windows so settings-provided variables are visible to authentication and other raw environment readers.

Bug Fixes:

  • Fix Windows environment updates so carrier changes are mirrored into the real process environment, restoring settings-based authentication and other raw environment reads.
  • Ensure invalid environment names remain ignored while valid writes and removals are reflected in the real Windows environment.

Enhancements:

  • Extend environment operation tracking and write-through behavior to Windows while preserving existing Unix behavior and transitional migration safeguards.

Documentation:

  • Update commit documentation to describe cross-platform environment mirroring and its safety and migration requirements.

Tests:

  • Replace the Windows test that expected the real environment to remain unchanged with assertions covering mirrored writes, removals, and rejected invalid names.

OriyukiKiri and others added 3 commits September 22, 2026 10:57
`EnvUpdate::commit` gated the real-environment write-through behind
`#[cfg(unix)]`, so on Windows every carrier write — settings.json `env`
included — stayed invisible to the raw `std::env` readers. That is why
`ANTHROPIC_AUTH_TOKEN` from settings.json never reached the API client
(`services/api/client.rs:84`) and every launch without a pre-exported token
failed with "Could not resolve authentication method". The carrier is a
one-time copy of the real environment (`process_env.rs:189-204`), so without
this bridge the two diverge and 516 raw `std::env` call sites read stale
values while only the 29 carrier readers stay correct.

Widen the eight `#[cfg(unix)]` gates that carry the write-through — the
`os_operations` field, its two queue sites, `begin_update`'s initializer, the
`OsOperation` enum, `EnvUpdate::set`/`remove`, and the `commit` loop — to
`#[cfg(any(unix, windows))]`. The operation log was already platform-ready:
`truncate_at_nul`/`contains_equals`/`keys_equal`/`array_index` all carry
Windows arms, and Windows `set_var` is documented sound, the same premise
`entrypoints/cli.rs:19-23` already relies on. The Unix arm is unchanged, so
Unix codegen is byte-identical and the existing `cfg(unix)` mirroring
assertions still hold.

Invert the Windows test that codified the defect as intent:
`windows_carrier_operations_leave_real_environment_unchanged` asserted the
real environment stays untouched, so it had to become
`windows_carrier_operations_mirror_into_real_environment` and assert the
carrier write lands.

Verified on Windows against three probes with the parent
`ANTHROPIC_AUTH_TOKEN` unset, so the credential could only come from a
settings file:

- settings-provided `ANTHROPIC_CUSTOM_HEADERS` went `present: false` ->
  `present: true`, and the request followed the settings-provided
  `ANTHROPIC_BASE_URL` rather than the inherited one;
- `--settings <alt settings.json>` — Flag source — returned a real
  completion end to end;
- `CLAUDE_CONFIG_DIR` — User source — stopped failing with the SDK's
  "Could not resolve authentication method".

Co-Authored-By: Claude <noreply@anthropic.com>
`EnvUpdate::commit` 的真实环境写回被 `#[cfg(unix)]` 门控,于是 Windows 上所有
carrier 写入——包括 settings.json 的 `env`——对裸 `std::env` 读取者都不可见。
这正是 settings.json 里的 `ANTHROPIC_AUTH_TOKEN` 始终到不了 API 客户端
(`services/api/client.rs:84`)、凡是没有预先导出该变量的启动一律报
"Could not resolve authentication method" 的原因。carrier 只是真实环境的一次性
拷贝(`process_env.rs:189-204`),缺了这座桥就会与真实环境分叉:516 处裸
`std::env` 读取点读到的是过期值,只有 29 处 carrier 读取者正确。

把承载写回的 8 个 `#[cfg(unix)]` 门放宽为 `#[cfg(any(unix, windows))]`:
`os_operations` 字段、两处入队点、`begin_update` 里的初始化、`OsOperation`
枚举、`EnvUpdate::set`/`remove`,以及 `commit` 的写回循环。操作日志本身早已
平台就绪——`truncate_at_nul`/`contains_equals`/`keys_equal`/`array_index` 都带
Windows 分支;而 Windows 的 `set_var` 被文档保证安全,这正是
`entrypoints/cli.rs:19-23` 已经在依赖的前提。Unix 分支逐字未改,故 Unix 生成
代码等价,既有的 `cfg(unix)` 镜像断言依然成立。

反转那个把缺陷固化成预期的 Windows 测试:
`windows_carrier_operations_leave_real_environment_unchanged` 断言真实环境保持
不变,必须改为 `windows_carrier_operations_mirror_into_real_environment`,并
断言 carrier 写入确实落地。

Windows 上以三个探针验收(父进程的 `ANTHROPIC_AUTH_TOKEN` 已 unset,凭据只能
来自 settings 文件):

- settings 提供的 `ANTHROPIC_CUSTOM_HEADERS` 由 `present: false` 变为
  `present: true`,且请求按 settings 提供的 `ANTHROPIC_BASE_URL` 发出,而非
  继承来的地址;
- `--settings <备用 settings.json>`(Flag 源)端到端返回真实回复;
- `CLAUDE_CONFIG_DIR`(User 源)不再报 SDK 的
  "Could not resolve authentication method"。

Co-Authored-By: Claude <noreply@anthropic.com>
…rough' into fix/windows-process-env-write-through
@sourcery-ai

sourcery-ai Bot commented Sep 22, 2026

Copy link
Copy Markdown

Reviewer's Guide

本 PR 将 EnvUpdate 的真实环境写回从仅 Unix 扩展到 Windows,复用既有规范化操作日志并在 commit 时同步 set/remove,从而让 settings.json 中的凭据和其他环境变量对裸 std::env 读取者可见;同时更新 Windows 测试以覆盖镜像、删除和非法名称行为。

Sequence diagram for cross-platform environment mirroring

sequenceDiagram
    participant Source as Settings or flag source
    participant Update as EnvUpdate
    participant Carrier as EnvTable carrier
    participant OS as Real process environment
    participant Reader as std::env readers

    Source->>Update: set(key, value)
    Update->>Carrier: stage normalized value
    Update->>Update: os_operations.push(OsOperation::Set)
    Update->>OS: commit -> std::env::set_var
    OS-->>Reader: value is visible

    Source->>Update: remove(key)
    Update->>Carrier: remove staged value
    Update->>Update: os_operations.push(OsOperation::Remove)
    Update->>OS: commit -> std::env::remove_var
    OS-->>Reader: value is cleared
Loading

Flow diagram for Windows environment synchronization and validation

flowchart LR
    A["settings.json env or flag"] --> B["EnvUpdate::set or remove"]
    B --> C["Normalize key and value"]
    C --> D["Update carrier and queue OsOperation"]
    D --> E["EnvUpdate::commit"]
    E --> F["Windows real process environment"]
    F --> G["裸 std::env readers"]

    C --> H["Invalid name"]
    H --> I["No carrier or OS update"]
Loading

File-Level Changes

Change Details Files
在 Windows 上启用 carrier 操作到真实进程环境的写回链路,使 settings.json 等来源写入的环境变量可被裸 std::env 读取者看到。
  • 将操作日志、更新入队和 commit 写回逻辑的条件编译从 Unix 扩展到 Unix/Windows。
  • 保留现有规范化、过滤和操作顺序,仅在 commit 边界调用 set_var/remove_var 镜像已规范化操作。
  • 更新 commit 注释,说明 Windows 安全依据及该过渡桥接的迁移前置条件。
src/utils/process_env.rs
将 Windows 测试从验证真实环境不变改为验证 carrier 与真实环境同步。
  • 覆盖合法值写入、删除清除,以及包含空键名和等号的非法名称不会落地。
  • 确认 NUL 截断后的规范化值同时可从 carrier 和 std::env 读取。
src/utils/process_env.rs

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/utils/process_env.rs" line_range="272" />
<code_context>
         staged,
-        #[cfg(unix)]
+        #[cfg(any(unix, windows))]
         os_operations: Vec::new(),
     }
 }
</code_context>
<issue_to_address>
**nitpick:** The `EnvVarGuard` contract in `src/utils/env_utils.rs` still describes the write-through as a temporary Unix-only bridge, but `os_operations` now enables that bridge on Windows as well. Maintainers reading the guard documentation are given false platform and production-behavior information.

**Triggers:** When a Windows test or future caller relies on the documented environment-restoration behavior.

**Suggested fix:** Update the `EnvVarGuard` documentation to describe the bridge as covering every supported platform, or explicitly explain any remaining Unix-only limitation.
</issue_to_address>

### Comment 2
<location path="src/utils/process_env.rs" line_range="852-856" />
<code_context>
-        assert_eq!(std::env::vars_os().collect::<Vec<_>>(), real_before);
-        assert_eq!(std::env::var_os(key), raw_value_before);

+        // Names normalization rejects reach neither the carrier nor the OS.
         let before_invalid = snapshot();
         set("", "ignored");
         set("BAD=KEY", "ignored");
         assert!(before_invalid.same_version(&snapshot()));
-        assert_eq!(std::env::vars_os().collect::<Vec<_>>(), real_before);

</code_context>
<issue_to_address>
**issue (testing):** The test claims invalid names reach neither the carrier nor the OS, but it only checks that the carrier snapshot version is unchanged; it never asserts that `std::env` remains unchanged for the invalid names. A regression that accidentally writes an invalid key to the real Windows environment while leaving the carrier unchanged passes this test.

**Triggers:** When the normalization-to-real-environment boundary regresses independently from carrier staging.

**Suggested fix:** Capture and assert the relevant real-environment state around the invalid-name writes, including the empty key and the key containing `=`.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 1 finding to address first, and the change writes carrier-managed variables into the real Windows process environment, allowing raw readers to use values such as ANTHROPIC_AUTH_TOKEN for authentication. If the synchronization is wrong, the process could use an incorrect or unintended credential, and reverting the code does not undo environment changes or any requests already made.

Blocking findings: src/utils/process_env.rs:856


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread src/utils/process_env.rs
staged,
#[cfg(unix)]
#[cfg(any(unix, windows))]
os_operations: Vec::new(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: The EnvVarGuard contract in src/utils/env_utils.rs still describes the write-through as a temporary Unix-only bridge, but os_operations now enables that bridge on Windows as well. Maintainers reading the guard documentation are given false platform and production-behavior information.

Triggers: When a Windows test or future caller relies on the documented environment-restoration behavior.

Suggested fix: Update the EnvVarGuard documentation to describe the bridge as covering every supported platform, or explicitly explain any remaining Unix-only limitation.

Comment thread src/utils/process_env.rs
Comment on lines +852 to 856
// Names normalization rejects reach neither the carrier nor the OS.
let before_invalid = snapshot();
set("", "ignored");
set("BAD=KEY", "ignored");
assert!(before_invalid.same_version(&snapshot()));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (testing): The test claims invalid names reach neither the carrier nor the OS, but it only checks that the carrier snapshot version is unchanged; it never asserts that std::env remains unchanged for the invalid names. A regression that accidentally writes an invalid key to the real Windows environment while leaving the carrier unchanged passes this test.

Triggers: When the normalization-to-real-environment boundary regresses independently from carrier staging.

Suggested fix: Capture and assert the relevant real-environment state around the invalid-name writes, including the empty key and the key containing =.

@Haleclipse

Copy link
Copy Markdown
Owner

English Plz!

@OriyukiKiri OriyukiKiri changed the title 修复(process_env): Windows 上把 carrier 写入同步到真实进程环境 fix(process_env): mirror carrier writes into the real env on Windows Sep 22, 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.

2 participants