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
This commit is contained in:
Eric Traut
2026-11-03 05:55:13 +00:00
committed by copyberry
parent 7ee5efad4a
commit 9e23a4708a
6 changed files with 59 additions and 32 deletions
+31 -4
View File
@@ -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:"))
+7 -3
View File
@@ -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"
+1 -1
View File
@@ -165,7 +165,7 @@ gcp_setup_wizard = true
allow_embedded_fallback: true,
endpoint: shared_endpoint.clone(),
},
[false, false],
[true, false],
),
(
"remote app server",
+5 -5
View File
@@ -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(),
+5 -5
View File
@@ -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
+10 -14
View File
@@ -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",