fix(process_env): mirror carrier writes into the real env on Windows - #11
OriyukiKiri wants to merge 3 commits into
Conversation
`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
Reviewer's Guide本 PR 将 EnvUpdate 的真实环境写回从仅 Unix 扩展到 Windows,复用既有规范化操作日志并在 commit 时同步 set/remove,从而让 settings.json 中的凭据和其他环境变量对裸 std::env 读取者可见;同时更新 Windows 测试以覆盖镜像、删除和非法名称行为。 Sequence diagram for cross-platform environment mirroringsequenceDiagram
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
Flow diagram for Windows environment synchronization and validationflowchart 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"]
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
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
| staged, | ||
| #[cfg(unix)] | ||
| #[cfg(any(unix, windows))] | ||
| os_operations: Vec::new(), |
There was a problem hiding this comment.
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.
| // 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())); |
There was a problem hiding this comment.
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 =.
|
English Plz! |
Summary
On Windows the real-environment write-through in
EnvUpdate::commitwas gated behind#[cfg(unix)],so the loop was compiled out of Windows builds entirely: the carrier was updated and
std::envwasnot. settings.json
env—ANTHROPIC_AUTH_TOKENincluded — therefore stayed invisible to the 516raw
std::envreaders 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))]:os_operationsfield,begin_update's initializer,enum OsOperationEnvUpdate::set,EnvUpdate::remove, bothrestore_entrybranchescommitThe operation log was already platform-ready (
truncate_at_nul/contains_equals/keys_equal/array_indexall carry#[cfg(windows)]arms), and Windowsset_varis documented sound — the samepremise
entrypoints/cli.rs:19-23already 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-persistenceBefore: both the user source (
CLAUDE_CONFIG_DIR) and the flag source (--settings) failed with theSDK's
Could not resolve authentication method.After:
ANTHROPIC_CUSTOM_HEADERSgoes frompresent: falsetopresent: trueANTHROPIC_BASE_URLfrom settings takes effect (connects to 19999, not the inherited 18936)--settingsfile completes a real round trip(
apiKeyHelperreads settings only, never env —utils/auth.rs:187— and it did take effect: proofthe 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 bothsides, names normalization rejects land on neither)
cargo test --lib process_env::— 14 passed / 0 failedSummary 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:
Enhancements:
Documentation:
Tests: