From 9e23a4708a769e7a4ad0b0ffef933950277e05fe Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Tue, 3 Nov 2026 05:39:14 +0000 Subject: [PATCH] Enable Bedrock onboarding through the local daemon (#59068) ## Why Bedrock onboarding previously forced an embedded server and was unavailable when connected to a local daemon. Credential discovery uses the server environment, which can differ from the launching shell. ## What changed - Allow the Bedrock setup wizard to use the local daemon while keeping GCP onboarding on the embedded server. - Clarify that detected AWS credentials come from the server environment. - Direct users who export AWS credentials in their shell to run `codex --no-daemon` in that shell. ## Testing Update wizard eligibility tests and onboarding snapshots, and extend daemon startup coverage to verify discovery of a Bedrock API key available only to an already running daemon. GitOrigin-RevId: 9502ffaa59250210e8f8bc8db6f82b0803cb6cac --- codex-rs/cli/tests/daemon_startup.rs | 35 +++++++++++++++++--- codex-rs/tui/src/lib.rs | 10 ++++-- codex-rs/tui/src/lib_tests.rs | 2 +- codex-rs/tui/src/onboarding/bedrock.rs | 10 +++--- codex-rs/tui/src/onboarding/bedrock_tests.rs | 10 +++--- codex-rs/tui/src/startup_orchestration.rs | 24 ++++++-------- 6 files changed, 59 insertions(+), 32 deletions(-) diff --git a/codex-rs/cli/tests/daemon_startup.rs b/codex-rs/cli/tests/daemon_startup.rs index 87b0cf590d..8a9900f7d6 100644 --- a/codex-rs/cli/tests/daemon_startup.rs +++ b/codex-rs/cli/tests/daemon_startup.rs @@ -154,7 +154,7 @@ async fn restrictive_launcher_uses_embedded_if_daemon_cannot_start() -> Result<( #[tokio::test] #[cfg(unix)] -async fn bedrock_onboarding_leaves_a_running_daemon_untouched() -> Result<()> { +async fn bedrock_onboarding_discovers_credentials_on_the_running_daemon() -> Result<()> { daemon_startup("bedrock-running").await } @@ -250,6 +250,7 @@ async fn daemon_startup(command: &str) -> Result<()> { | "agents-untrusted" | "agents-untrusted-new" | "discovered" + | "bedrock" | "bedrock-running" | "restrictive-job" ) || mismatch @@ -283,6 +284,14 @@ async fn daemon_startup(command: &str) -> Result<()> { let mut existing_daemon = if matches!(command, "bedrock-running" | "discovered" | "agents-no-daemon") || (mismatch && !disabling) { + if command == "bedrock-running" { + // This Unix-only case uses a daemon-only token and no ambient profiles. + env.insert("AWS_CONFIG_FILE".into(), "/dev/null".into()); + env.insert("AWS_SHARED_CREDENTIALS_FILE".into(), "/dev/null".into()); + env.remove("AWS_ACCESS_KEY_ID"); + env.remove("AWS_SECRET_ACCESS_KEY"); + env.insert("AWS_BEARER_TOKEN_BEDROCK".into(), "daemon-only-test-token".into()); + } let started = Command::new(&codex) .env_clear() .envs(&env) @@ -363,6 +372,11 @@ async fn daemon_startup(command: &str) -> Result<()> { steps.push_back(("GPT-5.6-Terra", b"/status\r")); if restart { "Server:Localbackgroundserver" } else { "Model:" } } + } else if command == "bedrock-running" { + env.remove("AWS_BEARER_TOKEN_BEDROCK"); + steps.push_back(("UseanAPIkeyoranotherprovider", b"3")); + steps.push_back(("UseAmazonBedrock", b"2")); + "Continuewithdetectedcredentials" } else if bedrock_onboarding { // With GCP disabled, only Bedrock makes the sign-in menu advertise another provider. "UseanAPIkeyoranotherprovider" @@ -445,7 +459,18 @@ async fn daemon_startup(command: &str) -> Result<()> { // A fresh disable request includes both cold daemon startup and its confirmed restart. let timeout = Duration::from_secs(if disabling { 90 } else { 45 }); let result = tokio::time::timeout(timeout, async { - while let Some(bytes) = stdout.recv().await { + loop { + let bytes = tokio::select! { + bytes = stdout.recv() => bytes, + // The first onboarding draw precedes its typeahead drain. Retry the + // main menu shortcut until the submenu appears (it has no option 3). + () = tokio::time::sleep(Duration::from_millis(/*millis*/ 250)), + if steps.front().is_some_and(|(ready, _)| *ready == "UseAmazonBedrock") => { + writer.send(b"3".to_vec()).await?; + continue; + } + }; + let Some(bytes) = bytes else { break }; output.push_str(&String::from_utf8_lossy(&bytes)); screen.process(&bytes); for (query, reply) in &mut queries { @@ -564,9 +589,11 @@ async fn daemon_startup(command: &str) -> Result<()> { } 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" { + } else if bedrock_onboarding { + ensure!(pid_file.exists() != elevated); + } else if command == "restrictive-job" { ensure!(!pid_file.exists()); - if command == "restrictive-job" && !elevated { + if !elevated { let contents = screen.screen().contents(); let warning = contents.lines() .find(|line| line.contains("Running without the shared background server:")) diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index 1c4d24a57e..c25fb9ff2e 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -1899,8 +1899,7 @@ async fn run_ratatui_app( if config.model_provider_id != startup_model_provider { startup_account = None; if matches!(&app_server_target, AppServerTarget::Embedded) { - // App-server providers are fixed at startup, so onboarding cannot - // reuse a server initialized before it persisted another provider. + // Rebuild the embedded server with the final local configuration. shutdown_app_server_if_present(app_server.take()).await; } } @@ -2520,7 +2519,12 @@ fn should_show_provider_setup_wizard( app_server_target: &AppServerTarget, feature: Feature, ) -> bool { - matches!(app_server_target, AppServerTarget::Embedded) + let supported_target = match app_server_target { + AppServerTarget::Embedded => true, + AppServerTarget::LocalDaemon { .. } => feature == Feature::BedrockSetupWizard, + AppServerTarget::Remote { .. } => false, + }; + supported_target && should_show_login_screen(login_status, requires_openai_auth) && config.features.enabled(feature) && config.model_provider_id == "openai" diff --git a/codex-rs/tui/src/lib_tests.rs b/codex-rs/tui/src/lib_tests.rs index faf27865a5..9516e002e1 100644 --- a/codex-rs/tui/src/lib_tests.rs +++ b/codex-rs/tui/src/lib_tests.rs @@ -165,7 +165,7 @@ gcp_setup_wizard = true allow_embedded_fallback: true, endpoint: shared_endpoint.clone(), }, - [false, false], + [true, false], ), ( "remote app server", diff --git a/codex-rs/tui/src/onboarding/bedrock.rs b/codex-rs/tui/src/onboarding/bedrock.rs index c986e03e56..51e6b2fc3b 100644 --- a/codex-rs/tui/src/onboarding/bedrock.rs +++ b/codex-rs/tui/src/onboarding/bedrock.rs @@ -392,7 +392,7 @@ impl BedrockState { } else if *list == BedrockMethodList::Detected && !self.environment_credentials.is_empty() { - lines.push(" AWS credentials detected in your environment.".into()); + lines.push(" AWS credentials detected in the server environment.".into()); } else { if *list == BedrockMethodList::Detected { lines.push(" No AWS credentials found.".into()); @@ -454,7 +454,7 @@ impl BedrockState { } BedrockView::EnvironmentInstructions => { lines.push( - " Configure AWS credentials in your environment, then restart Codex.".into(), + " Export AWS credentials, then run `codex --no-daemon` in that shell.".into(), ); lines.push("".into()); lines.push(Line::from(vec![ @@ -587,9 +587,9 @@ impl BedrockState { let description = if self.environment_credentials.iter().any(|credential| { credential.credential_type == AwsCredentialType::BedrockApiKey }) { - "Use your existing Amazon Bedrock API key" + "Use the Bedrock API key from the server environment" } else { - "Use your existing AWS credentials" + "Use AWS access keys from the server environment" }; ( "Continue with detected credentials".to_string(), @@ -620,7 +620,7 @@ impl BedrockState { ), BedrockMethod::EnvironmentInstructions => ( "Environment variables".to_string(), - "Configure AWS credentials in your environment, then return here".to_string(), + "Use AWS credentials exported in your shell".to_string(), ), BedrockMethod::ApiKey => ( "Bedrock API key".to_string(), diff --git a/codex-rs/tui/src/onboarding/bedrock_tests.rs b/codex-rs/tui/src/onboarding/bedrock_tests.rs index 44e6d64a56..591fe91825 100644 --- a/codex-rs/tui/src/onboarding/bedrock_tests.rs +++ b/codex-rs/tui/src/onboarding/bedrock_tests.rs @@ -193,7 +193,7 @@ fn discovery_prioritizes_profiles_and_groups_other_sign_in_methods() { Use your existing AWS credentials 2. Continue with detected credentials - Use your existing Amazon Bedrock API key + Use the Bedrock API key from the server environment 3. Other AWS sign-in methods Use another profile, access keys, environment variables, or a @@ -225,7 +225,7 @@ fn discovery_prioritizes_profiles_and_groups_other_sign_in_methods() { Enter an access key ID and secret access key 3. Environment variables - Configure AWS credentials in your environment, then return here + Use AWS credentials exported in your shell 4. Bedrock API key Enter a Bedrock API key @@ -300,7 +300,7 @@ fn discovery_prioritizes_profiles_and_groups_other_sign_in_methods() { insta::assert_snapshot!(render_visible(&environment_state), @r###" > Set up Amazon Bedrock - AWS credentials detected in your environment. + AWS credentials detected in the server environment. > 1. Continue with detected credentials @@ -328,7 +328,7 @@ fn discovery_prioritizes_profiles_and_groups_other_sign_in_methods() { Enter an access key ID and secret access key 3. Environment variables - Configure AWS credentials in your environment, then return here + Use AWS credentials exported in your shell 4. Bedrock API key Enter a Bedrock API key @@ -515,7 +515,7 @@ fn credential_entries_share_region_and_keep_aws_secrets_hidden() { insta::assert_snapshot!(render_visible(&access_keys), @r###" > Set up Amazon Bedrock - Configure AWS credentials in your environment, then restart Codex. + Export AWS credentials, then run `codex --no-daemon` in that shell. Setup guide: https://learn.chatgpt.com/docs/amazon-bedrock diff --git a/codex-rs/tui/src/startup_orchestration.rs b/codex-rs/tui/src/startup_orchestration.rs index 7b2a0221df..8e7076ab5b 100644 --- a/codex-rs/tui/src/startup_orchestration.rs +++ b/codex-rs/tui/src/startup_orchestration.rs @@ -573,17 +573,13 @@ pub(super) async fn run_main_inner( && !app_server_target.uses_remote_workspace(); if auto_start_daemon && daemon_exclusion.is_none() - && [Feature::BedrockSetupWizard, Feature::GcpSetupWizard] - .into_iter() - .any(|feature| { - should_show_provider_setup_wizard( - LoginStatus::NotAuthenticated, - config.model_provider.requires_openai_auth, - &config, - &AppServerTarget::Embedded, - feature, - ) - }) + && should_show_provider_setup_wizard( + LoginStatus::NotAuthenticated, + config.model_provider.requires_openai_auth, + &config, + &AppServerTarget::Embedded, + Feature::GcpSetupWizard, + ) && startup_draft .run_until( config @@ -595,8 +591,8 @@ pub(super) async fn run_main_inner( .flatten() .is_none() { - // Provider setup wizards configure their provider through the embedded server. - daemon_exclusion = Some("Cloud provider sign-in"); + // GCP setup resolves credential paths and runs gcloud in the launch environment. + daemon_exclusion = Some("GCP sign-in"); app_server_target = AppServerTarget::Embedded; } if auto_start_daemon @@ -803,7 +799,7 @@ pub(super) async fn run_main_inner( "config_override" } } - Some("Bedrock sign-in") => "bedrock_sign_in", + Some("GCP sign-in") => "gcp_sign_in", Some(daemon_startup::WSL_DRVFS_EXCLUSION) => "wsl_drvfs", Some("this Windows launcher") => "windows_detach_restricted", Some("daemon feature settings") => "daemon_compatibility",