mirror of
https://github.com/openai/codex.git
synced 2026-05-05 13:51:29 +03:00
## Why The argument-comment lint now has a packaged DotSlash artifact from [#15198](https://github.com/openai/codex/pull/15198), so the normal repo lint path should use that released payload instead of rebuilding the lint from source every time. That keeps `just clippy` and CI aligned with the shipped artifact while preserving a separate source-build path for people actively hacking on the lint crate. The current alpha package also exposed two integration wrinkles that the repo-side prebuilt wrapper needs to smooth over: - the bundled Dylint library filename includes the host triple, for example `@nightly-2025-09-18-aarch64-apple-darwin`, and Dylint derives `RUSTUP_TOOLCHAIN` from that filename - on Windows, Dylint's driver path also expects `RUSTUP_HOME` to be present in the environment Without those adjustments, the prebuilt CI jobs fail during `cargo metadata` or driver setup. This change makes the checked-in prebuilt wrapper normalize the packaged library name to the plain `nightly-2025-09-18` channel before invoking `cargo-dylint`, and it teaches both the wrapper and the packaged runner source to infer `RUSTUP_HOME` from `rustup show home` when the environment does not already provide it. After the prebuilt Windows lint job started running successfully, it also surfaced a handful of existing anonymous literal callsites in `windows-sandbox-rs`. This PR now annotates those callsites so the new cross-platform lint job is green on the current tree. ## What Changed - checked in the current `tools/argument-comment-lint/argument-comment-lint` DotSlash manifest - kept `tools/argument-comment-lint/run.sh` as the source-build wrapper for lint development - added `tools/argument-comment-lint/run-prebuilt-linter.sh` as the normal enforcement path, using the checked-in DotSlash package and bundled `cargo-dylint` - updated `just clippy` and `just argument-comment-lint` to use the prebuilt wrapper - split `.github/workflows/rust-ci.yml` so source-package checks live in a dedicated `argument_comment_lint_package` job, while the released lint runs in an `argument_comment_lint_prebuilt` matrix on Linux, macOS, and Windows - kept the pinned `nightly-2025-09-18` toolchain install in the prebuilt CI matrix, since the prebuilt package still relies on rustup-provided toolchain components - updated `tools/argument-comment-lint/run-prebuilt-linter.sh` to normalize host-qualified nightly library filenames, keep the `rustup` shim directory ahead of direct toolchain `cargo` binaries, and export `RUSTUP_HOME` when needed for Windows Dylint driver setup - updated `tools/argument-comment-lint/src/bin/argument-comment-lint.rs` so future published DotSlash artifacts apply the same nightly-filename normalization and `RUSTUP_HOME` inference internally - fixed the remaining Windows lint violations in `codex-rs/windows-sandbox-rs` by adding the required `/*param*/` comments at the reported callsites - documented the checked-in DotSlash file, wrapper split, archive layout, nightly prerequisite, and Windows `RUSTUP_HOME` requirement in `tools/argument-comment-lint/README.md`
449 lines
14 KiB
Rust
449 lines
14 KiB
Rust
use crate::config::Config;
|
|
use crate::config::ConfigToml;
|
|
use crate::config::edit::ConfigEditsBuilder;
|
|
use crate::config::profile::ConfigProfile;
|
|
use crate::config::types::WindowsSandboxModeToml;
|
|
use crate::default_client::originator;
|
|
use crate::protocol::SandboxPolicy;
|
|
use codex_features::Feature;
|
|
use codex_features::Features;
|
|
use codex_features::FeaturesToml;
|
|
use codex_otel::sanitize_metric_tag_value;
|
|
use codex_protocol::config_types::WindowsSandboxLevel;
|
|
use std::collections::BTreeMap;
|
|
use std::collections::HashMap;
|
|
use std::path::Path;
|
|
use std::path::PathBuf;
|
|
use std::time::Instant;
|
|
|
|
/// Kill switch for the elevated sandbox NUX on Windows.
|
|
///
|
|
/// When false, revert to the previous sandbox NUX, which only
|
|
/// prompts users to enable the legacy sandbox feature.
|
|
pub const ELEVATED_SANDBOX_NUX_ENABLED: bool = true;
|
|
|
|
pub trait WindowsSandboxLevelExt {
|
|
fn from_config(config: &Config) -> WindowsSandboxLevel;
|
|
fn from_features(features: &Features) -> WindowsSandboxLevel;
|
|
}
|
|
|
|
impl WindowsSandboxLevelExt for WindowsSandboxLevel {
|
|
fn from_config(config: &Config) -> WindowsSandboxLevel {
|
|
match config.permissions.windows_sandbox_mode {
|
|
Some(WindowsSandboxModeToml::Elevated) => WindowsSandboxLevel::Elevated,
|
|
Some(WindowsSandboxModeToml::Unelevated) => WindowsSandboxLevel::RestrictedToken,
|
|
None => Self::from_features(&config.features),
|
|
}
|
|
}
|
|
|
|
fn from_features(features: &Features) -> WindowsSandboxLevel {
|
|
if features.enabled(Feature::WindowsSandboxElevated) {
|
|
return WindowsSandboxLevel::Elevated;
|
|
}
|
|
if features.enabled(Feature::WindowsSandbox) {
|
|
WindowsSandboxLevel::RestrictedToken
|
|
} else {
|
|
WindowsSandboxLevel::Disabled
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn windows_sandbox_level_from_config(config: &Config) -> WindowsSandboxLevel {
|
|
WindowsSandboxLevel::from_config(config)
|
|
}
|
|
|
|
pub fn windows_sandbox_level_from_features(features: &Features) -> WindowsSandboxLevel {
|
|
WindowsSandboxLevel::from_features(features)
|
|
}
|
|
|
|
pub fn resolve_windows_sandbox_mode(
|
|
cfg: &ConfigToml,
|
|
profile: &ConfigProfile,
|
|
) -> Option<WindowsSandboxModeToml> {
|
|
if let Some(mode) = legacy_windows_sandbox_mode(profile.features.as_ref()) {
|
|
return Some(mode);
|
|
}
|
|
if legacy_windows_sandbox_keys_present(profile.features.as_ref()) {
|
|
return None;
|
|
}
|
|
|
|
profile
|
|
.windows
|
|
.as_ref()
|
|
.and_then(|windows| windows.sandbox)
|
|
.or_else(|| cfg.windows.as_ref().and_then(|windows| windows.sandbox))
|
|
.or_else(|| legacy_windows_sandbox_mode(cfg.features.as_ref()))
|
|
}
|
|
|
|
pub fn resolve_windows_sandbox_private_desktop(cfg: &ConfigToml, profile: &ConfigProfile) -> bool {
|
|
profile
|
|
.windows
|
|
.as_ref()
|
|
.and_then(|windows| windows.sandbox_private_desktop)
|
|
.or_else(|| {
|
|
cfg.windows
|
|
.as_ref()
|
|
.and_then(|windows| windows.sandbox_private_desktop)
|
|
})
|
|
.unwrap_or(true)
|
|
}
|
|
|
|
fn legacy_windows_sandbox_keys_present(features: Option<&FeaturesToml>) -> bool {
|
|
let Some(entries) = features.map(|features| &features.entries) else {
|
|
return false;
|
|
};
|
|
entries.contains_key(Feature::WindowsSandboxElevated.key())
|
|
|| entries.contains_key(Feature::WindowsSandbox.key())
|
|
|| entries.contains_key("enable_experimental_windows_sandbox")
|
|
}
|
|
|
|
pub fn legacy_windows_sandbox_mode(
|
|
features: Option<&FeaturesToml>,
|
|
) -> Option<WindowsSandboxModeToml> {
|
|
let entries = features.map(|features| &features.entries)?;
|
|
legacy_windows_sandbox_mode_from_entries(entries)
|
|
}
|
|
|
|
pub fn legacy_windows_sandbox_mode_from_entries(
|
|
entries: &BTreeMap<String, bool>,
|
|
) -> Option<WindowsSandboxModeToml> {
|
|
if entries
|
|
.get(Feature::WindowsSandboxElevated.key())
|
|
.copied()
|
|
.unwrap_or(false)
|
|
{
|
|
return Some(WindowsSandboxModeToml::Elevated);
|
|
}
|
|
if entries
|
|
.get(Feature::WindowsSandbox.key())
|
|
.copied()
|
|
.unwrap_or(false)
|
|
|| entries
|
|
.get("enable_experimental_windows_sandbox")
|
|
.copied()
|
|
.unwrap_or(false)
|
|
{
|
|
Some(WindowsSandboxModeToml::Unelevated)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
pub fn sandbox_setup_is_complete(codex_home: &Path) -> bool {
|
|
codex_windows_sandbox::sandbox_setup_is_complete(codex_home)
|
|
}
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
pub fn sandbox_setup_is_complete(_codex_home: &Path) -> bool {
|
|
false
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
pub fn elevated_setup_failure_details(err: &anyhow::Error) -> Option<(String, String)> {
|
|
let failure = codex_windows_sandbox::extract_setup_failure(err)?;
|
|
let code = failure.code.as_str().to_string();
|
|
let message = codex_windows_sandbox::sanitize_setup_metric_tag_value(&failure.message);
|
|
Some((code, message))
|
|
}
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
pub fn elevated_setup_failure_details(_err: &anyhow::Error) -> Option<(String, String)> {
|
|
None
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
pub fn elevated_setup_failure_metric_name(err: &anyhow::Error) -> &'static str {
|
|
if codex_windows_sandbox::extract_setup_failure(err).is_some_and(|failure| {
|
|
matches!(
|
|
failure.code,
|
|
codex_windows_sandbox::SetupErrorCode::OrchestratorHelperLaunchCanceled
|
|
)
|
|
}) {
|
|
"codex.windows_sandbox.elevated_setup_canceled"
|
|
} else {
|
|
"codex.windows_sandbox.elevated_setup_failure"
|
|
}
|
|
}
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
pub fn elevated_setup_failure_metric_name(_err: &anyhow::Error) -> &'static str {
|
|
panic!("elevated_setup_failure_metric_name is only supported on Windows")
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
pub fn run_elevated_setup(
|
|
policy: &SandboxPolicy,
|
|
policy_cwd: &Path,
|
|
command_cwd: &Path,
|
|
env_map: &HashMap<String, String>,
|
|
codex_home: &Path,
|
|
) -> anyhow::Result<()> {
|
|
codex_windows_sandbox::run_elevated_setup(
|
|
policy,
|
|
policy_cwd,
|
|
command_cwd,
|
|
env_map,
|
|
codex_home,
|
|
/*read_roots_override*/ None,
|
|
/*write_roots_override*/ None,
|
|
)
|
|
}
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
pub fn run_elevated_setup(
|
|
_policy: &SandboxPolicy,
|
|
_policy_cwd: &Path,
|
|
_command_cwd: &Path,
|
|
_env_map: &HashMap<String, String>,
|
|
_codex_home: &Path,
|
|
) -> anyhow::Result<()> {
|
|
anyhow::bail!("elevated Windows sandbox setup is only supported on Windows")
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
pub fn run_legacy_setup_preflight(
|
|
policy: &SandboxPolicy,
|
|
policy_cwd: &Path,
|
|
command_cwd: &Path,
|
|
env_map: &HashMap<String, String>,
|
|
codex_home: &Path,
|
|
) -> anyhow::Result<()> {
|
|
codex_windows_sandbox::run_windows_sandbox_legacy_preflight(
|
|
policy,
|
|
policy_cwd,
|
|
codex_home,
|
|
command_cwd,
|
|
env_map,
|
|
)
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
pub fn run_setup_refresh_with_extra_read_roots(
|
|
policy: &SandboxPolicy,
|
|
policy_cwd: &Path,
|
|
command_cwd: &Path,
|
|
env_map: &HashMap<String, String>,
|
|
codex_home: &Path,
|
|
extra_read_roots: Vec<PathBuf>,
|
|
) -> anyhow::Result<()> {
|
|
codex_windows_sandbox::run_setup_refresh_with_extra_read_roots(
|
|
policy,
|
|
policy_cwd,
|
|
command_cwd,
|
|
env_map,
|
|
codex_home,
|
|
extra_read_roots,
|
|
)
|
|
}
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
pub fn run_legacy_setup_preflight(
|
|
_policy: &SandboxPolicy,
|
|
_policy_cwd: &Path,
|
|
_command_cwd: &Path,
|
|
_env_map: &HashMap<String, String>,
|
|
_codex_home: &Path,
|
|
) -> anyhow::Result<()> {
|
|
anyhow::bail!("legacy Windows sandbox setup is only supported on Windows")
|
|
}
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
pub fn run_setup_refresh_with_extra_read_roots(
|
|
_policy: &SandboxPolicy,
|
|
_policy_cwd: &Path,
|
|
_command_cwd: &Path,
|
|
_env_map: &HashMap<String, String>,
|
|
_codex_home: &Path,
|
|
_extra_read_roots: Vec<PathBuf>,
|
|
) -> anyhow::Result<()> {
|
|
anyhow::bail!("Windows sandbox read-root refresh is only supported on Windows")
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum WindowsSandboxSetupMode {
|
|
Elevated,
|
|
Unelevated,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct WindowsSandboxSetupRequest {
|
|
pub mode: WindowsSandboxSetupMode,
|
|
pub policy: SandboxPolicy,
|
|
pub policy_cwd: PathBuf,
|
|
pub command_cwd: PathBuf,
|
|
pub env_map: HashMap<String, String>,
|
|
pub codex_home: PathBuf,
|
|
pub active_profile: Option<String>,
|
|
}
|
|
|
|
pub async fn run_windows_sandbox_setup(request: WindowsSandboxSetupRequest) -> anyhow::Result<()> {
|
|
let start = Instant::now();
|
|
let mode = request.mode;
|
|
let originator_tag = sanitize_metric_tag_value(originator().value.as_str());
|
|
let result = run_windows_sandbox_setup_and_persist(request).await;
|
|
|
|
match result {
|
|
Ok(()) => {
|
|
emit_windows_sandbox_setup_success_metrics(
|
|
mode,
|
|
originator_tag.as_str(),
|
|
start.elapsed(),
|
|
);
|
|
Ok(())
|
|
}
|
|
Err(err) => {
|
|
emit_windows_sandbox_setup_failure_metrics(
|
|
mode,
|
|
originator_tag.as_str(),
|
|
start.elapsed(),
|
|
&err,
|
|
);
|
|
Err(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn run_windows_sandbox_setup_and_persist(
|
|
request: WindowsSandboxSetupRequest,
|
|
) -> anyhow::Result<()> {
|
|
let mode = request.mode;
|
|
let policy = request.policy;
|
|
let policy_cwd = request.policy_cwd;
|
|
let command_cwd = request.command_cwd;
|
|
let env_map = request.env_map;
|
|
let codex_home = request.codex_home;
|
|
let active_profile = request.active_profile;
|
|
let setup_codex_home = codex_home.clone();
|
|
|
|
let setup_result = tokio::task::spawn_blocking(move || -> anyhow::Result<()> {
|
|
match mode {
|
|
WindowsSandboxSetupMode::Elevated => {
|
|
if !sandbox_setup_is_complete(setup_codex_home.as_path()) {
|
|
run_elevated_setup(
|
|
&policy,
|
|
policy_cwd.as_path(),
|
|
command_cwd.as_path(),
|
|
&env_map,
|
|
setup_codex_home.as_path(),
|
|
)?;
|
|
}
|
|
}
|
|
WindowsSandboxSetupMode::Unelevated => {
|
|
run_legacy_setup_preflight(
|
|
&policy,
|
|
policy_cwd.as_path(),
|
|
command_cwd.as_path(),
|
|
&env_map,
|
|
setup_codex_home.as_path(),
|
|
)?;
|
|
}
|
|
}
|
|
Ok(())
|
|
})
|
|
.await
|
|
.map_err(|join_err| anyhow::anyhow!("windows sandbox setup task failed: {join_err}"))?;
|
|
|
|
setup_result?;
|
|
|
|
ConfigEditsBuilder::new(codex_home.as_path())
|
|
.with_profile(active_profile.as_deref())
|
|
.set_windows_sandbox_mode(windows_sandbox_setup_mode_tag(mode))
|
|
.clear_legacy_windows_sandbox_keys()
|
|
.apply()
|
|
.await
|
|
.map_err(|err| anyhow::anyhow!("failed to persist windows sandbox mode: {err}"))
|
|
}
|
|
|
|
fn emit_windows_sandbox_setup_success_metrics(
|
|
mode: WindowsSandboxSetupMode,
|
|
originator_tag: &str,
|
|
duration: std::time::Duration,
|
|
) {
|
|
let Some(metrics) = codex_otel::metrics::global() else {
|
|
return;
|
|
};
|
|
let mode_tag = windows_sandbox_setup_mode_tag(mode);
|
|
let _ = metrics.record_duration(
|
|
"codex.windows_sandbox.setup_duration_ms",
|
|
duration,
|
|
&[
|
|
("result", "success"),
|
|
("originator", originator_tag),
|
|
("mode", mode_tag),
|
|
],
|
|
);
|
|
let _ = metrics.counter(
|
|
"codex.windows_sandbox.setup_success",
|
|
/*inc*/ 1,
|
|
&[("originator", originator_tag), ("mode", mode_tag)],
|
|
);
|
|
}
|
|
|
|
fn emit_windows_sandbox_setup_failure_metrics(
|
|
mode: WindowsSandboxSetupMode,
|
|
originator_tag: &str,
|
|
duration: std::time::Duration,
|
|
_err: &anyhow::Error,
|
|
) {
|
|
let Some(metrics) = codex_otel::metrics::global() else {
|
|
return;
|
|
};
|
|
let mode_tag = windows_sandbox_setup_mode_tag(mode);
|
|
let _ = metrics.record_duration(
|
|
"codex.windows_sandbox.setup_duration_ms",
|
|
duration,
|
|
&[
|
|
("result", "failure"),
|
|
("originator", originator_tag),
|
|
("mode", mode_tag),
|
|
],
|
|
);
|
|
let _ = metrics.counter(
|
|
"codex.windows_sandbox.setup_failure",
|
|
/*inc*/ 1,
|
|
&[("originator", originator_tag), ("mode", mode_tag)],
|
|
);
|
|
|
|
if matches!(mode, WindowsSandboxSetupMode::Elevated) {
|
|
#[cfg(target_os = "windows")]
|
|
{
|
|
let mut failure_tags: Vec<(&str, &str)> = vec![("originator", originator_tag)];
|
|
let mut code_tag: Option<String> = None;
|
|
let mut message_tag: Option<String> = None;
|
|
if let Some((code, message)) = elevated_setup_failure_details(_err) {
|
|
code_tag = Some(code);
|
|
message_tag = Some(message);
|
|
}
|
|
if let Some(code) = code_tag.as_deref() {
|
|
failure_tags.push(("code", code));
|
|
}
|
|
if let Some(message) = message_tag.as_deref() {
|
|
failure_tags.push(("message", message));
|
|
}
|
|
let _ = metrics.counter(
|
|
elevated_setup_failure_metric_name(_err),
|
|
/*inc*/ 1,
|
|
&failure_tags,
|
|
);
|
|
}
|
|
} else {
|
|
let _ = metrics.counter(
|
|
"codex.windows_sandbox.legacy_setup_preflight_failed",
|
|
/*inc*/ 1,
|
|
&[("originator", originator_tag)],
|
|
);
|
|
}
|
|
}
|
|
|
|
fn windows_sandbox_setup_mode_tag(mode: WindowsSandboxSetupMode) -> &'static str {
|
|
match mode {
|
|
WindowsSandboxSetupMode::Elevated => "elevated",
|
|
WindowsSandboxSetupMode::Unelevated => "unelevated",
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[path = "windows_sandbox_tests.rs"]
|
|
mod tests;
|