Fix Windows Bazel app-server trust tests (#16711)

## Why

Extracted from [#16528](https://github.com/openai/codex/pull/16528) so
the Windows Bazel app-server test failures can be reviewed independently
from the rest of that PR.

This PR targets:

-
`suite::v2::thread_shell_command::thread_shell_command_runs_as_standalone_turn_and_persists_history`
-
`suite::v2::thread_start::thread_start_with_elevated_sandbox_trusts_project_and_followup_loads_project_config`
-
`suite::v2::thread_start::thread_start_with_nested_git_cwd_trusts_repo_root`

There were two Windows-specific assumptions baked into those tests and
the underlying trust lookup:

- project trust keys were persisted and looked up using raw path
strings, but Bazel's Windows test environment can surface canonicalized
paths with `\\?\` / UNC prefixes or normalized symlink/junction targets,
so follow-up `thread/start` requests no longer matched the project entry
that had just been written
- `item/commandExecution/outputDelta` assertions compared exact trailing
line endings even though shell output chunk boundaries and CRLF handling
can differ on Windows, and Bazel made that timing-sensitive mismatch
visible

There was also one behavior bug separate from the assertion cleanup:
`thread/start` decided whether to persist trust from the final resolved
sandbox policy, but on Windows an explicit `workspace-write` request may
be downgraded to `read-only`. That incorrectly skipped writing trust
even though the request had asked to elevate the project, so the new
logic also keys off the requested sandbox mode.

## What

- Canonicalize project trust keys when persisting/loading `[projects]`
entries, while still accepting legacy raw keys for existing configs.
- Persist project trust when `thread/start` explicitly requests
`workspace-write` or `danger-full-access`, even if the resolved policy
is later downgraded on Windows.
- Make the Windows app-server tests compare persisted trust paths and
command output deltas in a path/newline-normalized way.

## Verification

- Existing app-server v2 tests cover the three failing Windows Bazel
cases above.
This commit is contained in:
Michael Bolin authored and GitHub committed 2026-04-03 21:41:25 +00:00
1 parent 567d2603b8
commit a70aee1a1e
5 files changed
+77 -24

No files matched your search

+17 -8
View File
@@ -12,6 +12,7 @@ use crate::config_loader::McpServerRequirement;
use crate::config_loader::ResidencyRequirement;
use crate::config_loader::Sourced;
use crate::config_loader::load_config_layers_state;
use crate::config_loader::project_trust_key;
use crate::memories::memory_root;
use crate::path_utils::normalize_for_native_workdir;
use crate::project_doc::DEFAULT_PROJECT_DOC_FILENAME;
@@ -92,7 +93,6 @@ use schemars::JsonSchema;
use serde::Deserialize;
use serde::Deserializer;
use serde::Serialize;
use similar::DiffableStr;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::io::ErrorKind;
@@ -1017,7 +1017,7 @@ pub(crate) fn set_project_trust_level_inner(
//
// [projects]
// "/path/to/project" = { trust_level = "trusted" }
let project_key = project_path.to_string_lossy().to_string();
let project_key = project_trust_key(project_path);
// Ensure top-level `projects` exists as a non-inline, explicit table. If it
// exists but was previously represented as a non-table (e.g., inline),
@@ -1745,18 +1745,27 @@ impl ConfigToml {
pub fn get_active_project(&self, resolved_cwd: &Path) -> Option<ProjectConfig> {
let projects = self.projects.clone().unwrap_or_default();
if let Some(project_config) = projects.get(&resolved_cwd.to_string_lossy().to_string()) {
let resolved_cwd_key = project_trust_key(resolved_cwd);
let resolved_cwd_raw_key = resolved_cwd.to_string_lossy().to_string();
if let Some(project_config) = projects
.get(&resolved_cwd_key)
.or_else(|| projects.get(&resolved_cwd_raw_key))
{
return Some(project_config.clone());
}
// If cwd lives inside a git repo/worktree, check whether the root git project
// (the primary repository working directory) is trusted. This lets
// worktrees inherit trust from the main project.
if let Some(repo_root) = resolve_root_git_project_for_trust(resolved_cwd)
&& let Some(project_config_for_root) =
projects.get(&repo_root.to_string_lossy().to_string_lossy().to_string())
{
return Some(project_config_for_root.clone());
if let Some(repo_root) = resolve_root_git_project_for_trust(resolved_cwd) {
let repo_root_key = project_trust_key(repo_root.as_path());
let repo_root_raw_key = repo_root.to_string_lossy().to_string();
if let Some(project_config_for_root) = projects
.get(&repo_root_key)
.or_else(|| projects.get(&repo_root_raw_key))
{
return Some(project_config_for_root.clone());
}
}
None