Respect configured authentication in the thread manager sample (#48004)

## Why

The sample hardcoded file-based credential storage and omitted configured auth routing and login restrictions. It should use the configured credentials, including secure storage in `cargo run` builds.

## What changed

- Load authentication settings through the shared bootstrap path and re-export the required helpers and types from `codex-core-api`.
- Honor CLI and MCP credential stores, `features.secret_auth_storage`, auth routing, the ChatGPT base URL, and system proxy settings.
- Preserve managed login-method and ChatGPT workspace restrictions while keeping the sample's built-in execution settings.
- Document the sample's authentication configuration behavior.

GitOrigin-RevId: b49883537acbebf9266b3970d2709e8e7d1bf217
This commit is contained in:
Celia Chen
2026-09-25 05:14:07 +00:00
committed by copyberry
parent aa380897f6
commit 8ae55c863d
3 changed files with 81 additions and 12 deletions
+6
View File
@@ -8,6 +8,9 @@ pub use codex_app_server_protocol::item_event_to_server_notification;
pub use codex_arg0::Arg0DispatchPaths;
pub use codex_arg0::arg0_dispatch_or_else;
pub use codex_config::ConfigLayerStack;
pub use codex_config::ConfigLoadOptions;
pub use codex_config::ConfigRequirements;
pub use codex_config::ConfigRequirementsToml;
pub use codex_config::config_toml::ProjectConfig;
pub use codex_config::config_toml::RealtimeAudioConfig;
pub use codex_config::config_toml::RealtimeConfig;
@@ -64,7 +67,10 @@ pub use codex_core::config::NetworkProxySpec;
pub use codex_core::config::Permissions;
pub use codex_core::config::TerminalResizeReflowConfig;
pub use codex_core::config::ThreadStoreConfig;
pub use codex_core::config::bootstrap_auth_config;
pub use codex_core::config::find_codex_home;
pub use codex_core::config::load_config_toml_with_layer_stack;
pub use codex_core::config::resolve_bootstrap_respect_system_proxy;
pub use codex_core::init_state_db;
pub use codex_core::local_agent_graph_store_from_state_db;
pub use codex_core::passthrough_image_store;
+6
View File
@@ -4,6 +4,12 @@ Small one-shot binary that starts a Codex thread with `ThreadManager` from
`codex-core-api`, submits a single user turn, and prints the final assistant
message.
Authentication uses the configured credential storage under `CODEX_HOME`, including
`cli_auth_credentials_store`, `mcp_oauth_credentials_store`, and
`features.secret_auth_storage`. Local managed authentication requirements and auth
routing settings are respected. This also applies to `cargo run` builds. Other
execution settings remain the sample's built-in configuration.
```sh
cargo run -p codex-thread-manager-sample -- "Say hello"
```
+69 -12
View File
@@ -13,7 +13,7 @@ use codex_core_api::AltScreenMode;
use codex_core_api::ApprovalsReviewer;
use codex_core_api::Arg0DispatchPaths;
use codex_core_api::AskForApproval;
use codex_core_api::AuthCredentialsStoreMode;
use codex_core_api::AuthKeyringBackendKind;
use codex_core_api::AuthManager;
use codex_core_api::AutoCompactTokenLimitScope;
use codex_core_api::CodexAppsToolsCache;
@@ -21,11 +21,15 @@ use codex_core_api::CodexHomeUserInstructionsProvider;
use codex_core_api::CodexThread;
use codex_core_api::Config;
use codex_core_api::ConfigLayerStack;
use codex_core_api::ConfigLoadOptions;
use codex_core_api::ConfigRequirements;
use codex_core_api::ConfigRequirementsToml;
use codex_core_api::Constrained;
use codex_core_api::EnvironmentManager;
use codex_core_api::EventMsg;
use codex_core_api::ExecServerRuntimePaths;
use codex_core_api::ExtensionRegistryBuilder;
use codex_core_api::Feature;
use codex_core_api::Features;
use codex_core_api::GhostSnapshotConfig;
use codex_core_api::History;
@@ -34,7 +38,6 @@ use codex_core_api::ModelAvailabilityNuxConfig;
use codex_core_api::MultiAgentV2Config;
use codex_core_api::NewThread;
use codex_core_api::Notice;
use codex_core_api::OAuthCredentialsStoreMode;
use codex_core_api::OPENAI_PROVIDER_ID;
use codex_core_api::OtelConfig;
use codex_core_api::PermissionProfile;
@@ -59,14 +62,17 @@ use codex_core_api::UriBasedFileOpener;
use codex_core_api::UserInput;
use codex_core_api::WebSearchMode;
use codex_core_api::arg0_dispatch_or_else;
use codex_core_api::bootstrap_auth_config;
use codex_core_api::build_models_manager;
use codex_core_api::built_in_model_providers;
use codex_core_api::find_codex_home;
use codex_core_api::init_state_db;
use codex_core_api::install_image_generation_extension;
use codex_core_api::item_event_to_server_notification;
use codex_core_api::load_config_toml_with_layer_stack;
use codex_core_api::local_agent_graph_store_from_state_db;
use codex_core_api::passthrough_image_store;
use codex_core_api::resolve_bootstrap_respect_system_proxy;
use codex_core_api::resolve_installation_id;
use codex_core_api::set_default_originator;
use codex_core_api::thread_store_from_config;
@@ -114,7 +120,7 @@ async fn run_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
args.prompt.join(" ")
};
let config = new_config(args.model, arg0_paths)?;
let config = new_config(args.model, arg0_paths).await?;
let state_db = init_state_db(&config).await;
let auth_manager =
@@ -176,9 +182,52 @@ async fn run_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
Ok(())
}
fn new_config(model: Option<String>, arg0_paths: Arg0DispatchPaths) -> anyhow::Result<Config> {
async fn new_config(
model: Option<String>,
arg0_paths: Arg0DispatchPaths,
) -> anyhow::Result<Config> {
let codex_home = find_codex_home().context("find Codex home")?;
let cwd = AbsolutePathBuf::current_dir().context("resolve current directory")?;
// Resolve auth through the shared bootstrap path so cargo-run builds retain the configured
// secure store instead of the full Config loader's local-development file-store override.
let bootstrap = load_config_toml_with_layer_stack(
codex_home.as_path(),
Some(&cwd),
Vec::new(),
ConfigLoadOptions::default(),
)
.await
.context("load authentication configuration")?;
let auth_config = bootstrap_auth_config(codex_home.as_path(), &bootstrap)?;
let requirements = bootstrap.config_layer_stack.requirements();
let requirements_toml = bootstrap.config_layer_stack.requirements_toml();
// Preserve auth restrictions for every manager rebuilt from Config without importing
// execution settings, plugins, or hooks from the bootstrap configuration.
let config_layer_stack = ConfigLayerStack::new(
Vec::new(),
ConfigRequirements {
allowed_login_methods: requirements.allowed_login_methods.clone(),
allowed_chatgpt_workspaces: requirements.allowed_chatgpt_workspaces.clone(),
..Default::default()
},
ConfigRequirementsToml {
allowed_login_methods: requirements_toml.allowed_login_methods.clone(),
allowed_chatgpt_workspaces: requirements_toml.allowed_chatgpt_workspaces.clone(),
..Default::default()
},
)?;
let mcp_oauth_credentials_store_mode = bootstrap
.config_toml
.mcp_oauth_credentials_store
.unwrap_or_default();
let respect_system_proxy = resolve_bootstrap_respect_system_proxy(
&bootstrap.config_toml,
bootstrap
.config_layer_stack
.requirements()
.feature_requirements
.as_ref(),
)?;
let model_provider_id = OPENAI_PROVIDER_ID.to_string();
let model_providers = built_in_model_providers(/*openai_base_url*/ None);
let model_provider = model_providers
@@ -188,8 +237,8 @@ fn new_config(model: Option<String>, arg0_paths: Arg0DispatchPaths) -> anyhow::R
let mut config = Config {
application_network_policy: Default::default(),
application_auth_route_config: None,
config_layer_stack: ConfigLayerStack::default(),
application_auth_route_config: Some(auth_config.auth_route_config.clone()),
config_layer_stack,
startup_warnings: Vec::new(),
bypass_hook_trust: false,
model,
@@ -256,11 +305,11 @@ fn new_config(model: Option<String>, arg0_paths: Arg0DispatchPaths) -> anyhow::R
cwd: cwd.clone(),
workspace_roots: vec![cwd],
workspace_roots_explicit: false,
cli_auth_credentials_store_mode: AuthCredentialsStoreMode::File,
cli_auth_credentials_store_mode: auth_config.auth_credentials_store_mode,
mcp_servers: Constrained::allow_any(HashMap::new()),
mcp_enterprise_managed_auth: None,
non_prefixed_mcp_tool_servers: None,
mcp_oauth_credentials_store_mode: OAuthCredentialsStoreMode::File,
mcp_oauth_credentials_store_mode,
mcp_oauth_callback_port: None,
mcp_oauth_callback_url: None,
mcp_optional_startup_grace: std::time::Duration::from_secs(1),
@@ -292,8 +341,11 @@ fn new_config(model: Option<String>, arg0_paths: Arg0DispatchPaths) -> anyhow::R
model_reasoning_summary: None,
model_catalog: None,
model_verbosity: None,
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
respect_system_proxy: false,
chatgpt_base_url: auth_config
.chatgpt_base_url
.clone()
.unwrap_or_else(|| "https://chatgpt.com/backend-api/".to_string()),
respect_system_proxy,
apps_mcp_product_sku: None,
responses_api_metadata: BTreeMap::new(),
realtime_audio: RealtimeAudioConfig::default(),
@@ -305,8 +357,8 @@ fn new_config(model: Option<String>, arg0_paths: Arg0DispatchPaths) -> anyhow::R
experimental_realtime_ws_startup_context: None,
experimental_realtime_start_instructions: None,
experimental_thread_store: ThreadStoreConfig::Local,
forced_chatgpt_workspace_id: None,
forced_login_method: None,
forced_chatgpt_workspace_id: auth_config.forced_chatgpt_workspace_id.clone(),
forced_login_method: auth_config.forced_login_method,
web_search_mode: Constrained::allow_any(WebSearchMode::Disabled),
web_search_config: None,
experimental_request_user_input_enabled: true,
@@ -339,6 +391,11 @@ fn new_config(model: Option<String>, arg0_paths: Arg0DispatchPaths) -> anyhow::R
.features
.set(Features::with_defaults())
.context("configure default features")?;
// AuthManager and MCP both derive their credential backend from Config.
config.features.set_enabled(
Feature::SecretAuthStorage,
auth_config.keyring_backend_kind == AuthKeyringBackendKind::Secrets,
)?;
Ok(config)
}