Use embedded mode for elevated Windows TUI sessions (#49855)

## Why

The Windows shared daemon rejects elevated launches so shared clients cannot inherit administrator privileges. Local TUI sessions launched as administrator should still be able to start.

## What changed

Select embedded mode before daemon discovery or startup for elevated local Windows sessions. Show a startup warning explaining that the shared background server is disabled and that restarting Codex in a non-elevated terminal enables it. Record `elevated_windows` as the server selection reason.

## Testing

Add Windows integration coverage for elevated normal, `resume`, and `fork` launches without creating daemon directories, and verify that explicit daemon `start` and `restart` still reject elevation. Add a snapshot test for the startup warning in the warning center.

GitOrigin-RevId: 89594e86d6ee19b080dfb11553cee8676395f188
This commit is contained in:
Eric Traut
2026-10-01 04:15:13 +00:00
committed by copyberry
parent 236be1ad9f
commit bc197b77bc
8 changed files with 140 additions and 19 deletions
@@ -88,7 +88,9 @@ pub(super) fn spawn_without_inheriting_stdio(
Ok(command.spawn()?)
}
pub(crate) fn ensure_not_elevated() -> Result<()> {
/// Reports whether this process has administrator privileges, rather than
/// merely belonging to an administrator account.
pub fn is_elevated() -> Result<bool> {
let mut token = 0;
if unsafe { OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token) } == 0 {
return Err(io::Error::last_os_error()).context("failed to query daemon launcher token");
@@ -109,8 +111,12 @@ pub(crate) fn ensure_not_elevated() -> Result<()> {
return Err(io::Error::last_os_error())
.context("failed to query daemon launcher elevation");
}
Ok(elevation.TokenIsElevated != 0)
}
pub(crate) fn ensure_not_elevated() -> Result<()> {
anyhow::ensure!(
elevation.TokenIsElevated == 0,
!is_elevated()?,
"start the Windows daemon from a non-elevated terminal; shared clients must not inherit administrator privileges"
);
Ok(())
+2
View File
@@ -5,6 +5,8 @@ mod background_command;
#[cfg(windows)]
pub use backend::windows::DetachedLaunchRestricted;
#[cfg(windows)]
pub use backend::windows::is_elevated;
#[cfg(windows)]
use backend::windows::try_lock_file;
mod client;
mod diagnostics;
+53 -13
View File
@@ -59,6 +59,37 @@ async fn daemon_auto_start_preserves_bedrock_onboarding() -> Result<()> {
daemon_startup("bedrock").await
}
#[tokio::test]
#[cfg(windows)]
async fn elevated_local_tui_uses_embedded_without_starting_daemon() -> Result<()> {
if !codex_app_server_daemon::is_elevated()? {
eprintln!("requires an elevated Windows runner");
return Ok(());
}
for command in ["elevated", "elevated-resume", "elevated-fork"] {
daemon_startup(command).await?;
}
let codex = codex_utils_cargo_bin::cargo_bin("codex")?;
let home = tempfile::tempdir()?;
for operation in ["start", "restart"] {
let output = Command::new(&codex)
.env("CODEX_HOME", home.path())
.args(["app-server", "daemon", operation])
.output()?;
ensure!(
!output.status.success(),
"{operation} must reject elevation"
);
ensure!(
String::from_utf8_lossy(&output.stderr).contains("non-elevated terminal"),
"{}",
String::from_utf8_lossy(&output.stderr)
);
}
ensure!(!home.path().join("app-server-daemon").exists());
Ok(())
}
#[tokio::test]
#[cfg(windows)]
async fn restrictive_launcher_uses_embedded_if_daemon_cannot_start() -> Result<()> {
@@ -97,6 +128,11 @@ async fn bedrock_onboarding_leaves_a_running_daemon_untouched() -> Result<()> {
}
async fn daemon_startup(command: &str) -> Result<()> {
#[cfg(windows)]
let elevated = codex_app_server_daemon::is_elevated()?;
#[cfg(not(windows))]
let elevated = false;
let elevated_launch = command.starts_with("elevated");
let codex = codex_utils_cargo_bin::cargo_bin("codex")?.canonicalize()?;
let workspace = tempfile::tempdir()?;
let workspace_path = workspace.path().canonicalize()?;
@@ -206,6 +242,13 @@ async fn daemon_startup(command: &str) -> Result<()> {
// The draft header is visible before the session's command composer is ready.
steps.push_back(("GPT-5.6-Terra", b"/status\r"));
"Server:Localbackgroundserver"
} else if elevated_launch || command == "restrictive-job" && elevated {
if let Some(action) = command.strip_prefix("elevated-") {
args.push(action.into());
steps.push_back(("Nosessionsyet", b"\x1b"));
}
steps.push_back(("GPT-5.6-Terra", b"\x14"));
"Runningasadministrator:sharedbackgroundserverdisabled."
} else if command == "restrictive-job" {
steps.push_back(("GPT-5.6-Terra", b"\x14"));
"Runningwithoutthesharedbackgroundserver:thisWindowslauncher"
@@ -234,7 +277,11 @@ async fn daemon_startup(command: &str) -> Result<()> {
args.extend([command.into(), "--strict-config".into()]);
steps.push_back(("Nosessionsyet", b"\x1b"));
steps.push_back(("GPT-5.6-Terra", b"\x14"));
"Runningwithoutthesharedbackgroundserver:--strict-config"
if elevated {
"Runningasadministrator:sharedbackgroundserverdisabled."
} else {
"Runningwithoutthesharedbackgroundserver:--strict-config"
}
};
let program = if cfg!(windows) && command == "restrictive-job" {
env.insert("CODEX_TEST_RESTRICTIVE_DAEMON_START".into(), "1".into());
@@ -260,7 +307,7 @@ async fn daemon_startup(command: &str) -> Result<()> {
codex_utils_pty::ChildFds::Inherited(&[]),
)
.await?;
let mut exit = spawned.exit_rx;
let exit = spawned.exit_rx;
let session = spawned.session;
let writer = session.writer_sender();
let mut stdout = spawned.stdout_rx;
@@ -293,16 +340,6 @@ async fn daemon_startup(command: &str) -> Result<()> {
.chars()
.filter(|c| !c.is_whitespace())
.collect();
if command == "restrictive-job"
&& text.contains("starttheWindowsdaemonfromanon-elevatedterminal")
{
// Elevated runners must still reject startup before the job probe.
ensure!(!pid_file.exists());
let status = tokio::time::timeout(Duration::from_secs(/*secs*/ 5), &mut exit)
.await??;
ensure!(status != 0, "elevated daemon launch must fail");
return Ok::<_, anyhow::Error>(());
}
if let Some((ready, input)) = steps.front()
&& text.contains(ready)
{
@@ -332,9 +369,12 @@ async fn daemon_startup(command: &str) -> Result<()> {
ensure!(home.path().join("app-server-daemon/daemon.pid").exists());
} else if let Some(existing_daemon) = &existing_daemon {
ensure!(fs::read(&pid_file)? == *existing_daemon);
} else if elevated_launch {
ensure!(!home.path().join("app-server-daemon").exists());
ensure!(!home.path().join("packages/app-server-daemon").exists());
} else if bedrock_onboarding || command == "restrictive-job" {
ensure!(!pid_file.exists());
if command == "restrictive-job" {
if command == "restrictive-job" && !elevated {
let contents = screen.screen().contents();
let warning = contents.lines()
.find(|line| line.contains("Running without the shared background server:"))
+4
View File
@@ -660,6 +660,10 @@ trust_level = "trusted"
("update_interval_setting", "default"),
("shutdown_grace_setting", "default"),
]);
#[cfg(windows)]
if codex_app_server_daemon::is_elevated()? {
expected_tags.insert("daemon_selection_reason", "elevated_windows");
}
if backend == "daemon" {
expected_tags.extend([
("auto_update", "disabled"),
@@ -0,0 +1,29 @@
---
source: tui/src/app/warning_notice_tests.rs
assertion_line: 116
expression: screen(&tui)
---
Warnings · 1 of 1 · Startup
Running as administrator: shared background server disabled. To enable it,
restart Codex in a terminal without administrator permissions.
k keep & next · esc dismiss & close · ctrl+o copy · ←/→ warning · ↓ scroll
@@ -96,3 +96,22 @@ async fn warning_notice_keeps_details_in_transcript_and_preserves_draft() -> Res
tui.set_owned_screen(/*owned*/ false)?;
Ok(())
}
#[tokio::test]
async fn elevated_launch_warning_appears_in_warning_center() -> Result<()> {
let mut app = crate::app::test_support::make_test_app().await;
let mut tui = crate::tui::test_support::make_test_tui()?;
tui.set_owned_screen(/*owned*/ true)?;
let size = Size::new(/*width*/ 80, /*height*/ 24);
app.insert_history_cell(
&mut tui,
Box::new(history_cell::StartupWarningsCell::new(vec![
crate::daemon_startup::ELEVATED_LAUNCH_WARNING.into(),
])),
);
app.chat_widget.open_warnings(&app.transcript_cells);
app.render_owned_transcript(&mut tui, size)?;
insta::assert_snapshot!("elevated_launch_warning_center", screen(&tui));
tui.set_owned_screen(/*owned*/ false)?;
Ok(())
}
+5 -1
View File
@@ -1,7 +1,8 @@
//! Local daemon launch policy. Explicit embedded launches never discover or start a daemon;
//! optional attachment may fall back to embedded mode, while automatic launches
//! require a compatible shared server and a successful connection, except when
//! the Windows launcher forbids detaching a missing server.
//! the Windows launcher forbids detaching a missing server. Elevated local
//! Windows sessions use explicit embedded behavior before discovery or startup.
use super::*;
use std::collections::BTreeMap;
@@ -15,6 +16,9 @@ const SERVER_FEATURES: [Feature; 4] = [
pub(super) const FAILURE_HINT: &str = "To work without the background server, rerun the same command with --no-daemon (including resume or fork and its arguments).";
#[cfg(any(windows, test))]
pub(super) const ELEVATED_LAUNCH_WARNING: &str = "Running as administrator: shared background server disabled. To enable it, restart Codex in a terminal without administrator permissions.";
#[derive(Debug, thiserror::Error)]
#[error("Cannot use the shared background server: {reason}.\n{FAILURE_HINT}")]
pub(super) struct CompatibilityError {
+20 -3
View File
@@ -23,6 +23,19 @@ pub(super) async fn run_main_inner(
"--add-dir is not supported with --remote. Configure additional workspace roots on the server.",
));
}
#[cfg(windows)]
let elevated_warning = if explicit_remote_endpoint.is_none()
&& !cli.no_daemon
&& !cli.agents_overview
&& codex_app_server_daemon::is_elevated().map_err(std::io::Error::other)?
{
cli.no_daemon = true;
Some(daemon_startup::ELEVATED_LAUNCH_WARNING)
} else {
None
};
#[cfg(not(windows))]
let elevated_warning: Option<&str> = None;
let strict_config = cli.strict_config;
if cli.shared.worktree {
if explicit_remote_endpoint.is_some() {
@@ -574,15 +587,18 @@ pub(super) async fn run_main_inner(
if app_server_target.uses_embedded_network_policy() {
embedded_network_policy.activate(&mut config);
}
let daemon_startup_warning = compatibility_warning.or_else(|| {
daemon_exclusion
let daemon_startup_warning = elevated_warning
.map(str::to_string)
.or(compatibility_warning)
.or_else(|| {
daemon_exclusion
.filter(|_| auto_start_daemon)
.map(|reason| {
format!(
"Running without the shared background server: {reason} requires embedded mode."
)
})
});
});
#[cfg(target_os = "macos")]
let local_runtime_paths = local_runtime_paths.with_allowed_symlinked_codex_home(
codex_config::allowed_symlinked_codex_home(&config.config_layer_stack, &config.codex_home),
@@ -644,6 +660,7 @@ pub(super) async fn run_main_inner(
let selection_reason = match (&app_server_target, daemon_exclusion) {
(AppServerTarget::Remote { .. }, _) => "explicit_remote",
_ if cli.agents_overview => "agents",
_ if elevated_warning.is_some() => "elevated_windows",
(_, Some("--no-daemon")) => "explicit_no_daemon",
(_, Some(_)) => "incompatible_option",
_ if auto_start_daemon => "auto_start",