[codex] request desktop attestation from app (#20619)

## Summary

TL;DR: teaches `codex-rs` / app-server to request a desktop-provided
attestation token and attach it as `x-oai-attestation` on the scoped
ChatGPT Codex request paths.

![DeviceCheck attestation
interface](https://raw.githubusercontent.com/openai/codex/dev/jm/devicecheck-diagram-assets/pr-assets/devicecheck-attestation-interface.png)

## Details

This PR teaches the Codex app-server runtime how to request and attach
an attestation token. It does not generate DeviceCheck tokens directly;
instead, it relies on the connected desktop app to advertise that it can
generate attestation and then asks that app for a fresh header value
when needed.

The flow is:

1. The Codex desktop app connects to app-server.
2. During `initialize`, the app can advertise that it supports
`requestAttestation`.
3. Before app-server calls selected ChatGPT Codex endpoints, it sends
the internal server request `attestation/generate` to the app.
4. app-server receives a pre-encoded header value back.
5. app-server forwards that value as `x-oai-attestation` on the scoped
outbound requests.

The code in this repo is mostly protocol and runtime plumbing: it adds
the app-server request/response shape, introduces an attestation
provider in core, wires that provider into Responses / compaction /
realtime setup paths, and covers the intended scoping with tests. The
signed macOS DeviceCheck generation remains owned by the desktop app PR.

## Related PR

- Codex desktop app implementation:
https://github.com/openai/openai/pull/878649

## Validation

<details>
<summary>Tests run</summary>

```sh
cargo test -p codex-app-server-protocol
cargo test -p codex-core attestation --lib
cargo test -p codex-app-server --lib attestation
```

Also ran:

```sh
just fix -p codex-core
just fix -p codex-app-server
just fix -p codex-app-server-protocol
just fmt
just write-app-server-schema
```

</details>

<details>
<summary>E2E DeviceCheck validation</summary>

First validated the signed desktop app boundary directly: launched a
packaged signed `Codex.app`, sent `attestation/generate`, decoded the
returned `v1.` attestation header, and validated the extracted
DeviceCheck token with `personal/jm/verify_devicecheck_token.py` using
bundle ID `com.openai.codex`. Apple returned `status_code: 200` and
`is_ok: true`.

Then ran the fuller app + app-server flow. The packaged `Codex.app`
launched a current-branch app-server via `CODEX_CLI_PATH`, and a local
MITM proxy intercepted outbound `chatgpt.com` traffic. The app-server
requested `attestation/generate` from the real Electron app process, and
the intercepted `/backend-api/codex/responses` traffic included
`x-oai-attestation` on both routes:

```text
GET  /backend-api/codex/responses  Upgrade: websocket  x-oai-attestation: present
POST /backend-api/codex/responses  Upgrade: none       x-oai-attestation: present
```

The captured header decoded to a DeviceCheck token that also validated
with Apple for `com.openai.codex` (`status_code: 200`, `is_ok: true`,
team `2DC432GLL2`).

</details>

---------

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Jiaming Zhang
2026-05-08 12:36:02 -07:00
committed by GitHub
co-authored by Codex
parent 61142b6169
commit 5f4d0ec343
65 changed files with 1086 additions and 39 deletions
+26
View File
@@ -0,0 +1,26 @@
use std::future::Future;
use std::pin::Pin;
use codex_protocol::ThreadId;
use http::HeaderValue;
pub(crate) const X_OAI_ATTESTATION_HEADER: &str = "x-oai-attestation";
pub type GenerateAttestationFuture<'a> =
Pin<Box<dyn Future<Output = Option<HeaderValue>> + Send + 'a>>;
/// Request context that host integrations can use when deciding whether to
/// generate an attestation header value.
#[derive(Clone, Copy, Debug)]
pub struct AttestationContext {
/// Thread whose upstream request is being prepared.
pub thread_id: ThreadId,
}
/// Host integration boundary for just-in-time attestation header values.
///
/// Implementations own the policy for when attestation should be attempted and
/// return the upstream `x-oai-attestation` header value when one should be sent.
pub trait AttestationProvider: std::fmt::Debug + Send + Sync {
fn header_for_request(&self, context: AttestationContext) -> GenerateAttestationFuture<'_>;
}
+50 -9
View File
@@ -105,6 +105,9 @@ use tracing::instrument;
use tracing::trace;
use tracing::warn;
use crate::attestation::AttestationContext;
use crate::attestation::AttestationProvider;
use crate::attestation::X_OAI_ATTESTATION_HEADER;
use crate::client_common::Prompt;
use crate::client_common::ResponseEvent;
use crate::client_common::ResponseStream;
@@ -170,6 +173,8 @@ struct ModelClientState {
enable_request_compression: bool,
include_timing_metrics: bool,
beta_features_header: Option<String>,
include_attestation: bool,
attestation_provider: Option<Arc<dyn AttestationProvider>>,
disable_websockets: AtomicBool,
cached_websocket_session: StdMutex<WebsocketSession>,
}
@@ -314,6 +319,7 @@ impl ModelClient {
enable_request_compression: bool,
include_timing_metrics: bool,
beta_features_header: Option<String>,
attestation_provider: Option<Arc<dyn AttestationProvider>>,
) -> Self {
let model_provider = create_model_provider(provider_info, auth_manager);
let codex_api_key_env_enabled = model_provider
@@ -322,6 +328,7 @@ impl ModelClient {
.is_some_and(|manager| manager.codex_api_key_env_enabled());
let auth_env_telemetry =
collect_auth_env_telemetry(model_provider.info(), codex_api_key_env_enabled);
let include_attestation = model_provider.supports_attestation();
Self {
state: Arc::new(ModelClientState {
session_id,
@@ -335,6 +342,8 @@ impl ModelClient {
enable_request_compression,
include_timing_metrics,
beta_features_header,
include_attestation,
attestation_provider,
disable_websockets: AtomicBool::new(false),
cached_websocket_session: StdMutex::new(WebsocketSession::default()),
}),
@@ -463,9 +472,6 @@ impl ModelClient {
text,
..
} = request;
let client =
ApiCompactClient::new(transport, client_setup.api_provider, client_setup.api_auth)
.with_telemetry(Some(request_telemetry));
let payload = ApiCompactionInput {
model: &model,
input: &input,
@@ -492,6 +498,12 @@ impl ModelClient {
Some(self.state.session_id.to_string()),
Some(self.state.thread_id.to_string()),
));
if let Some(header_value) = self.generate_attestation_header_for().await {
extra_headers.insert(X_OAI_ATTESTATION_HEADER, header_value);
}
let client =
ApiCompactClient::new(transport, client_setup.api_provider, client_setup.api_auth)
.with_telemetry(Some(request_telemetry));
let trace_attempt = compaction_trace.start_attempt(&payload);
let result = client
.compact_input(&payload, extra_headers)
@@ -505,11 +517,14 @@ impl ModelClient {
&self,
sdp: String,
session_config: ApiRealtimeSessionConfig,
extra_headers: ApiHeaderMap,
mut extra_headers: ApiHeaderMap,
) -> Result<RealtimeWebrtcCallStart> {
// Create the media call over HTTP first, then retain matching auth so realtime can attach
// the server-side control WebSocket to the call id from that HTTP response.
let client_setup = self.current_client_setup().await?;
if let Some(header_value) = self.generate_attestation_header_for().await {
extra_headers.insert(X_OAI_ATTESTATION_HEADER, header_value);
}
let mut sideband_headers = extra_headers.clone();
sideband_headers.extend(sideband_websocket_auth_headers(
client_setup.api_auth.as_ref(),
@@ -640,6 +655,20 @@ impl ModelClient {
client_metadata
}
async fn generate_attestation_header_for(&self) -> Option<HeaderValue> {
if !self.state.include_attestation {
return None;
}
self.state
.attestation_provider
.as_ref()?
.header_for_request(AttestationContext {
thread_id: self.state.thread_id,
})
.await
}
/// Builds request telemetry for unary API calls (e.g., Compact endpoint).
fn build_request_telemetry(
session_telemetry: &SessionTelemetry,
@@ -779,7 +808,9 @@ impl ModelClient {
auth_context: AuthRequestTelemetryContext,
request_route_telemetry: RequestRouteTelemetry,
) -> std::result::Result<ApiWebSocketConnection, ApiError> {
let headers = self.build_websocket_headers(turn_state.as_ref(), turn_metadata_header);
let headers = self
.build_websocket_headers(turn_state.as_ref(), turn_metadata_header)
.await;
let websocket_telemetry = ModelClientSession::build_websocket_telemetry(
session_telemetry,
auth_context,
@@ -856,7 +887,7 @@ impl ModelClient {
///
/// Callers should pass the current turn-state lock when available so sticky-routing state is
/// replayed on reconnect within the same turn.
fn build_websocket_headers(
async fn build_websocket_headers(
&self,
turn_state: Option<&Arc<OnceLock<String>>>,
turn_metadata_header: Option<&str>,
@@ -874,6 +905,9 @@ impl ModelClient {
}
headers.extend(build_session_headers(Some(session_id), Some(thread_id)));
headers.extend(self.build_responses_identity_headers());
if let Some(header_value) = self.generate_attestation_header_for().await {
headers.insert(X_OAI_ATTESTATION_HEADER, header_value);
}
headers.insert(
OPENAI_BETA_HEADER,
HeaderValue::from_static(RESPONSES_WEBSOCKETS_V2_BETA_HEADER_VALUE),
@@ -922,7 +956,7 @@ impl ModelClientSession {
///
/// Keeping option construction in one place ensures request-scoped headers are consistent
/// regardless of transport choice.
fn build_responses_options(
async fn build_responses_options(
&self,
turn_metadata_header: Option<&str>,
compression: Compression,
@@ -941,6 +975,9 @@ impl ModelClientSession {
turn_metadata_header.as_ref(),
);
headers.extend(self.client.build_responses_identity_headers());
if let Some(header_value) = self.client.generate_attestation_header_for().await {
headers.insert(X_OAI_ATTESTATION_HEADER, header_value);
}
headers
},
compression,
@@ -1217,7 +1254,9 @@ impl ModelClientSession {
self.client.state.auth_env_telemetry.clone(),
);
let compression = self.responses_request_compression(client_setup.auth.as_ref());
let options = self.build_responses_options(turn_metadata_header, compression);
let options = self
.build_responses_options(turn_metadata_header, compression)
.await;
let request = self.client.build_responses_request(
&client_setup.api_provider,
@@ -1324,7 +1363,9 @@ impl ModelClientSession {
);
let compression = self.responses_request_compression(client_setup.auth.as_ref());
let options = self.build_responses_options(turn_metadata_header, compression);
let options = self
.build_responses_options(turn_metadata_header, compression)
.await;
let request = self.client.build_responses_request(
&client_setup.api_provider,
prompt,
+115
View File
@@ -7,13 +7,21 @@ use super::X_CODEX_PARENT_THREAD_ID_HEADER;
use super::X_CODEX_TURN_METADATA_HEADER;
use super::X_CODEX_WINDOW_ID_HEADER;
use super::X_OPENAI_SUBAGENT_HEADER;
use crate::AttestationContext;
use crate::AttestationProvider;
use crate::GenerateAttestationFuture;
use codex_api::ApiError;
use codex_api::ResponseEvent;
use codex_app_server_protocol::AuthMode;
use codex_login::AuthManager;
use codex_login::CodexAuth;
use codex_model_provider::BearerAuthProvider;
use codex_model_provider_info::CHATGPT_CODEX_BASE_URL;
use codex_model_provider_info::ModelProviderInfo;
use codex_model_provider_info::WireApi;
use codex_model_provider_info::create_oss_provider_with_base_url;
use codex_otel::SessionTelemetry;
use codex_protocol::SessionId;
use codex_protocol::ThreadId;
use codex_protocol::models::ContentItem;
use codex_protocol::models::ResponseItem;
@@ -36,6 +44,8 @@ use std::collections::VecDeque;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use std::task::Context;
use std::task::Poll;
use std::time::Duration;
@@ -64,6 +74,7 @@ fn test_model_client(session_source: SessionSource) -> ModelClient {
/*enable_request_compression*/ false,
/*include_timing_metrics*/ false,
/*beta_features_header*/ None,
/*attestation_provider*/ None,
)
}
@@ -466,3 +477,107 @@ fn auth_request_telemetry_context_tracks_attached_auth_and_retry_phase() {
assert_eq!(auth_context.recovery_mode, Some("managed"));
assert_eq!(auth_context.recovery_phase, Some("refresh_token"));
}
fn model_client_with_counting_attestation(
include_attestation: bool,
) -> (ModelClient, Arc<AtomicUsize>) {
#[derive(Debug)]
struct CountingAttestationProvider {
calls: Arc<AtomicUsize>,
}
impl AttestationProvider for CountingAttestationProvider {
fn header_for_request(
&self,
_context: AttestationContext,
) -> GenerateAttestationFuture<'_> {
let calls = self.calls.clone();
Box::pin(async move {
let call = calls.fetch_add(1, Ordering::Relaxed) + 1;
Some(http::HeaderValue::from_bytes(format!("v1.header-{call}").as_bytes()).unwrap())
})
}
}
let attestation_calls = Arc::new(AtomicUsize::new(0));
let (auth_manager, provider) = if include_attestation {
(
Some(AuthManager::from_auth_for_testing(
CodexAuth::create_dummy_chatgpt_auth_for_testing(),
)),
ModelProviderInfo::create_openai_provider(Some(CHATGPT_CODEX_BASE_URL.to_string())),
)
} else {
(
None,
create_oss_provider_with_base_url("https://example.com/v1", WireApi::Responses),
)
};
let model_client = ModelClient::new(
auth_manager,
SessionId::new(),
ThreadId::new(),
/*installation_id*/ "11111111-1111-4111-8111-111111111111".to_string(),
provider,
SessionSource::Exec,
/*model_verbosity*/ None,
/*enable_request_compression*/ false,
/*include_timing_metrics*/ false,
/*beta_features_header*/ None,
Some(Arc::new(CountingAttestationProvider {
calls: attestation_calls.clone(),
})),
);
(model_client, attestation_calls)
}
#[tokio::test]
async fn websocket_handshake_includes_attestation_for_chatgpt_codex_responses() {
let (model_client, attestation_calls) =
model_client_with_counting_attestation(/*include_attestation*/ true);
let headers = model_client
.build_websocket_headers(/*turn_state*/ None, /*turn_metadata_header*/ None)
.await;
assert_eq!(
headers
.get(crate::attestation::X_OAI_ATTESTATION_HEADER)
.and_then(|value| value.to_str().ok()),
Some("v1.header-1"),
);
assert_eq!(attestation_calls.load(Ordering::Relaxed), 1);
}
#[tokio::test]
async fn non_chatgpt_codex_endpoints_omit_attestation_generation() {
let (model_client, attestation_calls) =
model_client_with_counting_attestation(/*include_attestation*/ false);
let mut response_headers = http::HeaderMap::new();
if let Some(header_value) = model_client.generate_attestation_header_for().await {
response_headers.insert(crate::attestation::X_OAI_ATTESTATION_HEADER, header_value);
}
let mut compaction_headers = http::HeaderMap::new();
if let Some(header_value) = model_client.generate_attestation_header_for().await {
compaction_headers.insert(crate::attestation::X_OAI_ATTESTATION_HEADER, header_value);
}
let mut realtime_headers = http::HeaderMap::new();
if let Some(header_value) = model_client.generate_attestation_header_for().await {
realtime_headers.insert(crate::attestation::X_OAI_ATTESTATION_HEADER, header_value);
}
assert_eq!(
response_headers.get(crate::attestation::X_OAI_ATTESTATION_HEADER),
None,
);
assert_eq!(
compaction_headers.get(crate::attestation::X_OAI_ATTESTATION_HEADER),
None,
);
assert_eq!(
realtime_headers.get(crate::attestation::X_OAI_ATTESTATION_HEADER),
None,
);
assert_eq!(attestation_calls.load(Ordering::Relaxed), 0);
}
+1
View File
@@ -99,6 +99,7 @@ pub(crate) async fn run_codex_thread_interactive(
environment_selections: parent_ctx.environments.clone(),
analytics_events_client: Some(parent_session.services.analytics_events_client.clone()),
thread_store: Arc::clone(&parent_session.services.thread_store),
attestation_provider: parent_session.services.attestation_provider.clone(),
}))
.or_cancel(&cancel_token)
.await??;
+4
View File
@@ -23,6 +23,7 @@ pub use codex_thread::CodexThread;
pub use codex_thread::CodexThreadTurnContextOverrides;
pub use codex_thread::ThreadConfigSnapshot;
mod agent;
mod attestation;
mod codex_delegate;
mod command_canonicalization;
mod commit_attribution;
@@ -177,6 +178,9 @@ mod tasks;
mod user_shell_command;
pub mod util;
pub use attestation::AttestationContext;
pub use attestation::AttestationProvider;
pub use attestation::GenerateAttestationFuture;
pub use client::ModelClient;
pub use client::ModelClientSession;
pub use client::X_CODEX_INSTALLATION_ID_HEADER;
+1
View File
@@ -53,6 +53,7 @@ pub async fn build_prompt_input(
thread_store,
state_db.clone(),
installation_id,
/*attestation_provider*/ None,
);
let thread = thread_manager.start_thread(config).await?;
+4
View File
@@ -14,6 +14,7 @@ use crate::agent::Mailbox;
use crate::agent::MailboxReceiver;
use crate::agent::agent_status_from_event;
use crate::agent::status::is_final;
use crate::attestation::AttestationProvider;
use crate::build_available_skills;
use crate::commit_attribution::commit_message_trailer_instruction;
use crate::compact;
@@ -412,6 +413,7 @@ pub(crate) struct CodexSpawnArgs {
pub(crate) environment_selections: ResolvedTurnEnvironments,
pub(crate) analytics_events_client: Option<AnalyticsEventsClient>,
pub(crate) thread_store: Arc<dyn ThreadStore>,
pub(crate) attestation_provider: Option<Arc<dyn AttestationProvider>>,
}
pub(crate) const INITIAL_SUBMIT_ID: &str = "";
@@ -471,6 +473,7 @@ impl Codex {
environment_selections,
analytics_events_client,
thread_store,
attestation_provider,
} = args;
let (tx_sub, rx_sub) = async_channel::bounded(SUBMISSION_CHANNEL_CAPACITY);
let (tx_event, rx_event) = async_channel::unbounded();
@@ -656,6 +659,7 @@ impl Codex {
analytics_events_client,
thread_store,
parent_rollout_thread_trace,
attestation_provider,
)
.await
.map_err(|e| {
+3
View File
@@ -370,6 +370,7 @@ impl Session {
analytics_events_client: Option<AnalyticsEventsClient>,
thread_store: Arc<dyn ThreadStore>,
parent_rollout_thread_trace: ThreadTraceContext,
attestation_provider: Option<Arc<dyn AttestationProvider>>,
) -> anyhow::Result<Arc<Self>> {
debug!(
"Configuring session: model={}; provider={:?}",
@@ -852,6 +853,7 @@ impl Session {
state_db: state_db_ctx.clone(),
live_thread: live_thread_init.as_ref().cloned(),
thread_store: Arc::clone(&thread_store),
attestation_provider: attestation_provider.clone(),
model_client: ModelClient::new(
Some(Arc::clone(&auth_manager)),
session_id,
@@ -863,6 +865,7 @@ impl Session {
config.features.enabled(Feature::EnableRequestCompression),
config.features.enabled(Feature::RuntimeMetrics),
Self::build_model_client_beta_features_header(config.as_ref()),
attestation_provider,
),
code_mode_service: crate::tools::code_mode::CodeModeService::new(),
environment_manager,
+8
View File
@@ -407,6 +407,7 @@ fn test_model_client_session() -> crate::client::ModelClientSession {
/*enable_request_compression*/ false,
/*include_timing_metrics*/ false,
/*beta_features_header*/ None,
/*attestation_provider*/ None,
)
.new_session()
}
@@ -3733,6 +3734,7 @@ async fn session_new_fails_when_zsh_fork_enabled_without_zsh_path() {
/*state_db*/ None,
)),
codex_rollout_trace::ThreadTraceContext::disabled(),
/*attestation_provider*/ None,
)
.await;
@@ -3881,6 +3883,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
codex_thread_store::LocalThreadStoreConfig::from_config(config.as_ref()),
/*state_db*/ None,
)),
attestation_provider: None,
model_client: ModelClient::new(
Some(auth_manager.clone()),
thread_id.into(),
@@ -3892,6 +3895,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
config.features.enabled(Feature::EnableRequestCompression),
config.features.enabled(Feature::RuntimeMetrics),
Session::build_model_client_beta_features_header(config.as_ref()),
/*attestation_provider*/ None,
),
code_mode_service: crate::tools::code_mode::CodeModeService::new(),
environment_manager: Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
@@ -4069,6 +4073,7 @@ async fn make_session_with_config_and_rx(
/*state_db*/ None,
)),
codex_rollout_trace::ThreadTraceContext::disabled(),
/*attestation_provider*/ None,
)
.await?;
@@ -4178,6 +4183,7 @@ async fn make_session_with_history_source_and_agent_control_and_rx(
),
)),
codex_rollout_trace::ThreadTraceContext::disabled(),
/*attestation_provider*/ None,
)
.await?;
@@ -5596,6 +5602,7 @@ where
codex_thread_store::LocalThreadStoreConfig::from_config(config.as_ref()),
state_db,
)),
attestation_provider: None,
model_client: ModelClient::new(
Some(Arc::clone(&auth_manager)),
thread_id.into(),
@@ -5607,6 +5614,7 @@ where
config.features.enabled(Feature::EnableRequestCompression),
config.features.enabled(Feature::RuntimeMetrics),
Session::build_model_client_beta_features_header(config.as_ref()),
/*attestation_provider*/ None,
),
code_mode_service: crate::tools::code_mode::CodeModeService::new(),
environment_manager: Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
@@ -763,6 +763,7 @@ async fn guardian_subagent_does_not_inherit_parent_exec_policy_rules() {
},
analytics_events_client: None,
thread_store,
attestation_provider: None,
})
.await
.expect("spawn guardian subagent");
+2
View File
@@ -3,6 +3,7 @@ use std::sync::Arc;
use crate::SkillsManager;
use crate::agent::AgentControl;
use crate::attestation::AttestationProvider;
use crate::client::ModelClient;
use crate::config::StartedNetworkProxy;
use crate::exec_policy::ExecPolicyManager;
@@ -66,6 +67,7 @@ pub(crate) struct SessionServices {
pub(crate) state_db: Option<StateDbHandle>,
pub(crate) live_thread: Option<LiveThread>,
pub(crate) thread_store: Arc<dyn ThreadStore>,
pub(crate) attestation_provider: Option<Arc<dyn AttestationProvider>>,
/// Session-scoped model client shared across turns.
pub(crate) model_client: ModelClient,
pub(crate) code_mode_service: CodeModeService,
+6
View File
@@ -1,5 +1,6 @@
use crate::SkillsManager;
use crate::agent::AgentControl;
use crate::attestation::AttestationProvider;
use crate::codex_thread::CodexThread;
use crate::config::Config;
use crate::config::ThreadStoreConfig;
@@ -248,6 +249,7 @@ pub(crate) struct ThreadManagerState {
mcp_manager: Arc<McpManager>,
skills_watcher: Arc<SkillsWatcher>,
thread_store: Arc<dyn ThreadStore>,
attestation_provider: Option<Arc<dyn AttestationProvider>>,
session_source: SessionSource,
installation_id: String,
analytics_events_client: Option<AnalyticsEventsClient>,
@@ -291,6 +293,7 @@ impl ThreadManager {
thread_store: Arc<dyn ThreadStore>,
state_db: Option<StateDbHandle>,
installation_id: String,
attestation_provider: Option<Arc<dyn AttestationProvider>>,
) -> Self {
let codex_home = config.codex_home.clone();
let restriction_product = session_source.restriction_product();
@@ -317,6 +320,7 @@ impl ThreadManager {
mcp_manager,
skills_watcher,
thread_store,
attestation_provider,
auth_manager,
session_source,
installation_id,
@@ -418,6 +422,7 @@ impl ThreadManager {
mcp_manager,
skills_watcher,
thread_store,
attestation_provider: None,
auth_manager,
session_source: SessionSource::Exec,
installation_id,
@@ -1204,6 +1209,7 @@ impl ThreadManagerState {
environment_selections,
analytics_events_client: self.analytics_events_client.clone(),
thread_store: Arc::clone(&self.thread_store),
attestation_provider: self.attestation_provider.clone(),
})
.await?;
let new_thread = self
+11
View File
@@ -495,6 +495,7 @@ async fn resume_and_fork_do_not_restore_thread_environments_from_rollout() {
thread_store_from_config(&config, /*state_db*/ None),
/*state_db*/ None,
TEST_INSTALLATION_ID.to_string(),
/*attestation_provider*/ None,
);
let selected_cwd =
AbsolutePathBuf::try_from(config.cwd.as_path().join("selected")).expect("absolute path");
@@ -611,6 +612,7 @@ async fn explicit_installation_id_skips_codex_home_file() {
thread_store,
state_db.clone(),
installation_id.clone(),
/*attestation_provider*/ None,
);
let thread = manager
@@ -648,6 +650,7 @@ async fn resume_active_thread_from_rollout_returns_running_thread() {
thread_store_from_config(&config, /*state_db*/ None),
/*state_db*/ None,
TEST_INSTALLATION_ID.to_string(),
/*attestation_provider*/ None,
);
let source = manager
@@ -703,6 +706,7 @@ async fn resume_stopped_thread_from_rollout_spawns_new_thread() {
thread_store_from_config(&config, /*state_db*/ None),
/*state_db*/ None,
TEST_INSTALLATION_ID.to_string(),
/*attestation_provider*/ None,
);
let source = manager
@@ -765,6 +769,7 @@ async fn resume_stopped_thread_from_rollout_preserves_thread_source() {
thread_store,
state_db.clone(),
TEST_INSTALLATION_ID.to_string(),
/*attestation_provider*/ None,
);
let source = manager
@@ -853,6 +858,7 @@ async fn rollout_path_resume_and_fork_read_history_through_thread_store() {
thread_store.clone(),
state_db,
TEST_INSTALLATION_ID.to_string(),
/*attestation_provider*/ None,
);
let source = manager
@@ -954,6 +960,7 @@ async fn new_uses_active_provider_for_model_refresh() {
thread_store_from_config(&config, /*state_db*/ None),
/*state_db*/ None,
TEST_INSTALLATION_ID.to_string(),
/*attestation_provider*/ None,
);
let _ = manager.list_models(RefreshStrategy::Online).await;
@@ -1168,6 +1175,7 @@ async fn interrupted_fork_snapshot_does_not_synthesize_turn_id_for_legacy_histor
thread_store_from_config(&config, state_db.clone()),
state_db.clone(),
TEST_INSTALLATION_ID.to_string(),
/*attestation_provider*/ None,
);
let source = manager
@@ -1274,6 +1282,7 @@ async fn interrupted_fork_snapshot_preserves_explicit_turn_id() {
thread_store_from_config(&config, state_db.clone()),
state_db.clone(),
TEST_INSTALLATION_ID.to_string(),
/*attestation_provider*/ None,
);
let source = manager
@@ -1369,6 +1378,7 @@ async fn interrupted_fork_snapshot_uses_persisted_mid_turn_history_without_live_
thread_store_from_config(&config, state_db.clone()),
state_db.clone(),
TEST_INSTALLATION_ID.to_string(),
/*attestation_provider*/ None,
);
let source = manager
@@ -1510,6 +1520,7 @@ async fn resumed_thread_keeps_paused_goal_paused() -> anyhow::Result<()> {
thread_store_from_config(&config, state_db.clone()),
state_db.clone(),
TEST_INSTALLATION_ID.to_string(),
/*attestation_provider*/ None,
);
let source = manager
@@ -3167,6 +3167,7 @@ async fn tool_handlers_cascade_close_and_resume_and_keep_explicitly_closed_subtr
thread_store_from_config(&config, state_db.clone()),
state_db.clone(),
"11111111-1111-4111-8111-111111111111".to_string(),
/*attestation_provider*/ None,
);
let parent = manager