Add attributed MCP OAuth credential storage telemetry (#49392)

## What changed

- Instrument MCP OAuth loads, saves, deletes, fallback cleanup, and refresh persistence with credential-free storage metrics, distinguishing configured policy from operations on a pinned store.
- Retain the originating client across background tasks, blocking storage operations, and session recovery, including recovery after startup found no credentials.
- Preserve typed keyring and lock error categories for metrics and remove raw error details from storage warnings.

## Testing

Add tests for policy and pinned-store outcomes, best-effort cleanup failures, wrapped lock errors, and successful or failed refresh persistence without duplicate observations. Add recovery tests that verify client attribution after anonymous startup and across threads with different originators.

GitOrigin-RevId: 5ea4528bcdcf500c8ffdf1d9c7008ea52112c726
This commit is contained in:
Celia Chen
2026-09-29 23:01:46 +00:00
committed by copyberry
parent 9212b3eca8
commit 05ea5f757e
25 changed files with 1279 additions and 187 deletions
+13 -8
View File
@@ -65,6 +65,7 @@ use codex_config::McpServerTransportConfig;
use codex_config::McpStartupReadiness;
use codex_diagnostics::Gauge;
use codex_diagnostics::GaugeGuard;
use codex_otel::auth_storage::AuthStorageOriginator;
use codex_protocol::mcp::CallToolResult;
use codex_protocol::mcp::McpServerInfo;
use codex_protocol::protocol::Event;
@@ -772,6 +773,7 @@ impl McpConnectionSet {
(server_name, outcome)
};
let startup = AuthStorageOriginator::current().scope(startup);
if defer_startup {
// Dormant servers must not hold the initial startup summary open.
tokio::spawn(startup);
@@ -893,15 +895,18 @@ impl McpConnectionSet {
return Vec::new();
}
let originator = AuthStorageOriginator::current();
match tokio::task::spawn_blocking(move || {
candidates
.into_iter()
.filter_map(|(server_name, identity, config)| {
identity
.oauth_credentials_changed(&server_name, &config)
.then_some(server_name)
})
.collect()
originator.sync_scope(|| {
candidates
.into_iter()
.filter_map(|(server_name, identity, config)| {
identity
.oauth_credentials_changed(&server_name, &config)
.then_some(server_name)
})
.collect()
})
})
.await
{
+7 -5
View File
@@ -57,6 +57,7 @@ use codex_connectors::ConnectorRuntimeContext;
use codex_connectors::ConnectorRuntimeFetchSource;
use codex_exec_server::Environment;
use codex_login::AuthChangeState;
use codex_otel::auth_storage::AuthStorageOriginator;
use codex_protocol::mcp::ClientMcpExtensions;
use codex_protocol::mcp::McpServerInfo;
use codex_protocol::protocol::Event;
@@ -286,6 +287,7 @@ fn codex_apps_reconnect_backoff(consecutive_failures: u32) -> Duration {
#[derive(Clone)]
struct ManagedClientStartup {
originator: AuthStorageOriginator,
server_name: String,
server: EffectiveMcpServer,
store_mode: OAuthCredentialsStoreMode,
@@ -316,6 +318,7 @@ impl ManagedClientStartup {
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = None;
let Self {
originator,
server_name,
server,
store_mode,
@@ -343,7 +346,7 @@ impl ManagedClientStartup {
.startup_timeout_sec
.unwrap_or(DEFAULT_STARTUP_TIMEOUT);
let cancel_token_for_fut = cancel_token;
async move {
let startup = async move {
let tool_catalog_fetch_ticket = tool_catalog_cache_context
.as_ref()
.map(McpToolCatalogCacheContext::begin_fetch);
@@ -420,10 +423,8 @@ impl ManagedClientStartup {
startup_complete.store(true, Ordering::Release);
outcome
}
.in_current_span()
.boxed()
.shared()
};
originator.scope(startup).in_current_span().boxed().shared()
}
}
@@ -480,6 +481,7 @@ impl AsyncManagedClient {
let startup_complete = Arc::new(AtomicBool::new(false));
let server_capabilities = Arc::new(StdMutex::new(None));
let startup = Arc::new(ManagedClientStartup {
originator: AuthStorageOriginator::current(),
server_name,
server,
store_mode,