diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index ab630ef467da..9461a61fe98f 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1853,6 +1853,7 @@ version = "0.0.0" dependencies = [ "codex-protocol", "codex-state", + "codex-utils-absolute-path", "pretty_assertions", "serde", "serde_json", @@ -2766,6 +2767,7 @@ dependencies = [ "codex-model-provider-info", "codex-models-manager", "codex-protocol", + "codex-state", "codex-utils-absolute-path", ] @@ -3167,6 +3169,7 @@ dependencies = [ "codex-protocol", "codex-state", "codex-tools", + "codex-utils-absolute-path", "codex-utils-template", "pretty_assertions", "serde", diff --git a/codex-rs/agent-graph-store/Cargo.toml b/codex-rs/agent-graph-store/Cargo.toml index 45bcb84c4739..1bb5ed2699fb 100644 --- a/codex-rs/agent-graph-store/Cargo.toml +++ b/codex-rs/agent-graph-store/Cargo.toml @@ -19,6 +19,7 @@ serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } [dev-dependencies] +codex-utils-absolute-path = { workspace = true } pretty_assertions = { workspace = true } serde_json = { workspace = true } tempfile = { workspace = true } diff --git a/codex-rs/agent-graph-store/src/local.rs b/codex-rs/agent-graph-store/src/local.rs index 56cb1f4ac46b..a7c1fd4a3395 100644 --- a/codex-rs/agent-graph-store/src/local.rs +++ b/codex-rs/agent-graph-store/src/local.rs @@ -16,7 +16,7 @@ pub struct LocalAgentGraphStore { impl std::fmt::Debug for LocalAgentGraphStore { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("LocalAgentGraphStore") - .field("codex_home", &self.state_db.codex_home()) + .field("sqlite", self.state_db.sqlite()) .finish_non_exhaustive() } } @@ -126,6 +126,7 @@ fn internal_error(err: impl std::fmt::Display) -> AgentGraphStoreError { mod tests { use super::*; use codex_state::DirectionalThreadSpawnEdgeStatus; + use codex_utils_absolute_path::test_support::PathExt; use pretty_assertions::assert_eq; use tempfile::TempDir; @@ -141,10 +142,12 @@ mod tests { async fn state_runtime() -> TestRuntime { let codex_home = TempDir::new().expect("tempdir should be created"); - let state_db = - StateRuntime::init(codex_home.path().to_path_buf(), "test-provider".to_string()) - .await - .expect("state db should initialize"); + let state_db = StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()), + "test-provider".to_string(), + ) + .await + .expect("state db should initialize"); TestRuntime { state_db, _codex_home: codex_home, diff --git a/codex-rs/app-server-transport/src/transport/remote_control/enroll.rs b/codex-rs/app-server-transport/src/transport/remote_control/enroll.rs index c99191378187..be10fffb84c7 100644 --- a/codex-rs/app-server-transport/src/transport/remote_control/enroll.rs +++ b/codex-rs/app-server-transport/src/transport/remote_control/enroll.rs @@ -432,6 +432,7 @@ mod tests { use crate::transport::remote_control::protocol::normalize_remote_control_url; use crate::transport::remote_control::server_api::enroll_remote_control_server; use codex_state::StateRuntime; + use codex_utils_absolute_path::test_support::PathExt; use pretty_assertions::assert_eq; use serde_json::json; use std::sync::Arc; @@ -445,9 +446,12 @@ mod tests { use tokio::time::timeout; async fn remote_control_state_runtime(codex_home: &TempDir) -> Arc { - StateRuntime::init(codex_home.path().to_path_buf(), "test-provider".to_string()) - .await - .expect("state runtime should initialize") + StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()), + "test-provider".to_string(), + ) + .await + .expect("state runtime should initialize") } #[test] diff --git a/codex-rs/app-server-transport/src/transport/remote_control/tests.rs b/codex-rs/app-server-transport/src/transport/remote_control/tests.rs index 328759faeb0f..bc8fe7a7a0c5 100644 --- a/codex-rs/app-server-transport/src/transport/remote_control/tests.rs +++ b/codex-rs/app-server-transport/src/transport/remote_control/tests.rs @@ -39,6 +39,7 @@ use codex_login::token_data::parse_chatgpt_jwt_claims; use codex_protocol::auth::AuthMode; use codex_state::RemoteControlEnrollmentRecord; use codex_state::StateRuntime; +use codex_utils_absolute_path::test_support::PathExt; use futures::SinkExt; use futures::StreamExt; use gethostname::gethostname; @@ -126,9 +127,12 @@ fn remote_control_auth_dot_json(account_id: Option<&str>) -> AuthDotJson { } async fn remote_control_state_runtime(codex_home: &TempDir) -> Arc { - StateRuntime::init(codex_home.path().to_path_buf(), "test-provider".to_string()) - .await - .expect("state runtime should initialize") + StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()), + "test-provider".to_string(), + ) + .await + .expect("state runtime should initialize") } #[tokio::test] diff --git a/codex-rs/app-server-transport/src/transport/remote_control/websocket.rs b/codex-rs/app-server-transport/src/transport/remote_control/websocket.rs index b45d71dd2e63..84f5affe6d4c 100644 --- a/codex-rs/app-server-transport/src/transport/remote_control/websocket.rs +++ b/codex-rs/app-server-transport/src/transport/remote_control/websocket.rs @@ -1836,6 +1836,7 @@ mod tests { use codex_login::token_data::parse_chatgpt_jwt_claims; use codex_protocol::auth::AuthMode; use codex_state::StateRuntime; + use codex_utils_absolute_path::test_support::PathExt; use futures::StreamExt; use pretty_assertions::assert_eq; use std::sync::Arc; @@ -1994,9 +1995,12 @@ mod tests { } pub(super) async fn remote_control_state_runtime(codex_home: &TempDir) -> Arc { - StateRuntime::init(codex_home.path().to_path_buf(), "test-provider".to_string()) - .await - .expect("state runtime should initialize") + StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()), + "test-provider".to_string(), + ) + .await + .expect("state runtime should initialize") } pub(super) fn remote_control_auth_manager() -> Arc { diff --git a/codex-rs/app-server/src/lib.rs b/codex-rs/app-server/src/lib.rs index 169a6ca73ed0..24d0f94a567a 100644 --- a/codex-rs/app-server/src/lib.rs +++ b/codex-rs/app-server/src/lib.rs @@ -569,7 +569,7 @@ pub async fn run_main_with_transport_options( Err(err) => { return Err(std::io::Error::other(format!( "failed to initialize sqlite state runtime under {}: {err}", - config.sqlite_home.display() + config.sqlite_config().home().display() ))); } }; @@ -1196,16 +1196,8 @@ async fn init_sqlite_state_db_with_fresh_start_on_corruption( } Err(err) => err, }; - let database_path = - codex_state::runtime_db_path_for_corruption_error(&err).unwrap_or_else(|| { - codex_state::SqliteConfig::from_sqlite_home( - codex_utils_absolute_path::AbsolutePathBuf::resolve_path_against_base( - config.sqlite_home.clone(), - &config.codex_home, - ), - ) - .state_db_path() - }); + let database_path = codex_state::runtime_db_path_for_corruption_error(&err) + .unwrap_or_else(|| config.sqlite_config().state_db_path()); if !codex_state::is_sqlite_corruption_error(&err) && !sqlite_home_is_blocking_file(database_path.as_path()) { diff --git a/codex-rs/app-server/tests/suite/v2/external_agent_config.rs b/codex-rs/app-server/tests/suite/v2/external_agent_config.rs index ce1c493397d2..f2ff5e077b8c 100644 --- a/codex-rs/app-server/tests/suite/v2/external_agent_config.rs +++ b/codex-rs/app-server/tests/suite/v2/external_agent_config.rs @@ -1,3 +1,4 @@ +use codex_utils_absolute_path::test_support::PathExt; use std::time::Duration; use anyhow::Result; @@ -538,9 +539,11 @@ async fn external_agent_config_import_sends_completion_notification_for_sync_onl ) .await??; assert_eq!(completed.import_id, import_id); - let state_db = - codex_state::StateRuntime::init(sqlite_home.path().to_path_buf(), "mock_provider".into()) - .await?; + let state_db = codex_state::StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(sqlite_home.path().abs()), + "mock_provider".into(), + ) + .await?; let details_record = state_db .external_agent_config_import_details_record(&import_id) .await? diff --git a/codex-rs/app-server/tests/suite/v2/memory_reset.rs b/codex-rs/app-server/tests/suite/v2/memory_reset.rs index 6408f318d4a3..8e89fa9c20fc 100644 --- a/codex-rs/app-server/tests/suite/v2/memory_reset.rs +++ b/codex-rs/app-server/tests/suite/v2/memory_reset.rs @@ -9,6 +9,7 @@ use codex_protocol::protocol::SessionSource; use codex_state::Stage1JobClaimOutcome; use codex_state::StateRuntime; use codex_state::ThreadMetadataBuilder; +use codex_utils_absolute_path::test_support::PathExt; use pretty_assertions::assert_eq; use std::path::Path; use std::sync::Arc; @@ -120,7 +121,11 @@ async fn seed_stage1_output(state_db: &Arc, codex_home: &Path) -> } async fn init_state_db(codex_home: &Path) -> Result> { - let state_db = StateRuntime::init(codex_home.to_path_buf(), "mock_provider".into()).await?; + let state_db = StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(codex_home.abs()), + "mock_provider".into(), + ) + .await?; state_db .mark_backfill_complete(/*last_watermark*/ None) .await?; diff --git a/codex-rs/app-server/tests/suite/v2/remote_control.rs b/codex-rs/app-server/tests/suite/v2/remote_control.rs index 5f9ab44592ed..b8ec92952a4d 100644 --- a/codex-rs/app-server/tests/suite/v2/remote_control.rs +++ b/codex-rs/app-server/tests/suite/v2/remote_control.rs @@ -1,3 +1,4 @@ +use codex_utils_absolute_path::test_support::PathExt; use std::ffi::OsStr; use std::ffi::OsString; use std::io::ErrorKind; @@ -254,8 +255,11 @@ async fn listen_off_honors_persisted_remote_control_enable() -> Result<()> { "ws://{}/backend-api/wham/remote/control/server", listener.local_addr()? ); - let state_db = - StateRuntime::init(codex_home.path().to_path_buf(), "test-provider".to_string()).await?; + let state_db = StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()), + "test-provider".to_string(), + ) + .await?; state_db .upsert_remote_control_enrollment(&RemoteControlEnrollmentRecord { websocket_url, @@ -298,8 +302,11 @@ async fn listen_off_ignores_persisted_enable_when_disabled_by_requirements() -> "ws://{}/backend-api/wham/remote/control/server", listener.local_addr()? ); - let state_db = - StateRuntime::init(codex_home.path().to_path_buf(), "test-provider".to_string()).await?; + let state_db = StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()), + "test-provider".to_string(), + ) + .await?; state_db .upsert_remote_control_enrollment(&RemoteControlEnrollmentRecord { websocket_url: websocket_url.clone(), @@ -348,9 +355,11 @@ async fn listen_off_exits_without_persisted_remote_control_enable() -> Result<() "ws://{}/backend-api/wham/remote/control/server", listener.local_addr()? ); - let state_db = - StateRuntime::init(codex_home.path().to_path_buf(), "test-provider".to_string()) - .await?; + let state_db = StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()), + "test-provider".to_string(), + ) + .await?; state_db .upsert_remote_control_enrollment(&RemoteControlEnrollmentRecord { websocket_url, @@ -460,8 +469,11 @@ async fn disable_waits_for_in_flight_durable_enable() -> Result<()> { let codex_home = TempDir::new()?; let mut backend = BlockingRemoteControlBackend::start(codex_home.path()).await?; let websocket_url = backend.websocket_url().to_string(); - let state_db = - StateRuntime::init(codex_home.path().to_path_buf(), "test-provider".to_string()).await?; + let state_db = StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()), + "test-provider".to_string(), + ) + .await?; let mut mcp = TestAppServer::builder() .with_codex_home(codex_home.path()) .without_auto_env() @@ -494,8 +506,11 @@ async fn rpc_updates_durable_preference_but_ephemeral_does_not() -> Result<()> { let codex_home = TempDir::new()?; let mut backend = BlockingRemoteControlBackend::start(codex_home.path()).await?; let websocket_url = backend.websocket_url().to_string(); - let state_db = - StateRuntime::init(codex_home.path().to_path_buf(), "test-provider".to_string()).await?; + let state_db = StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()), + "test-provider".to_string(), + ) + .await?; let mut mcp = TestAppServer::builder() .with_codex_home(codex_home.path()) diff --git a/codex-rs/app-server/tests/suite/v2/thread_archive.rs b/codex-rs/app-server/tests/suite/v2/thread_archive.rs index 51c1a993779c..f850adb38475 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_archive.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_archive.rs @@ -25,6 +25,7 @@ use codex_core::find_thread_path_by_id_str; use codex_protocol::ThreadId; use codex_state::DirectionalThreadSpawnEdgeStatus; use codex_state::StateRuntime; +use codex_utils_absolute_path::test_support::PathExt; use pretty_assertions::assert_eq; use std::path::Path; use tempfile::TempDir; @@ -186,8 +187,11 @@ async fn thread_archive_archives_spawned_descendants() -> Result<()> { let parent_thread_id = ThreadId::from_string(&parent_id)?; let child_thread_id = ThreadId::from_string(&child_id)?; let grandchild_thread_id = ThreadId::from_string(&grandchild_id)?; - let state_db = - StateRuntime::init(codex_home.path().to_path_buf(), "mock_provider".into()).await?; + let state_db = StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()), + "mock_provider".into(), + ) + .await?; state_db .mark_backfill_complete(/*last_watermark*/ None) .await?; @@ -292,8 +296,11 @@ async fn thread_archive_succeeds_when_descendant_archive_fails() -> Result<()> { let parent_thread_id = ThreadId::from_string(&parent_id)?; let child_thread_id = ThreadId::from_string(&child_id)?; let grandchild_thread_id = ThreadId::from_string(&grandchild_id)?; - let state_db = - StateRuntime::init(codex_home.path().to_path_buf(), "mock_provider".into()).await?; + let state_db = StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()), + "mock_provider".into(), + ) + .await?; state_db .mark_backfill_complete(/*last_watermark*/ None) .await?; @@ -408,8 +415,11 @@ async fn thread_archive_succeeds_when_spawned_descendant_is_missing() -> Result< let parent_thread_id = ThreadId::from_string(&parent_id)?; let missing_child_thread_id = ThreadId::from_string("00000000-0000-0000-0000-000000000901")?; - let state_db = - StateRuntime::init(codex_home.path().to_path_buf(), "mock_provider".into()).await?; + let state_db = StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()), + "mock_provider".into(), + ) + .await?; state_db .mark_backfill_complete(/*last_watermark*/ None) .await?; diff --git a/codex-rs/app-server/tests/suite/v2/thread_delete.rs b/codex-rs/app-server/tests/suite/v2/thread_delete.rs index c3aa91cb2573..44e5096d1d3f 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_delete.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_delete.rs @@ -15,7 +15,9 @@ use codex_core::find_thread_path_by_id_str; use codex_protocol::ThreadId; use codex_protocol::protocol::HistoryPosition; use codex_state::DirectionalThreadSpawnEdgeStatus; +use codex_state::SqliteConfig; use codex_state::StateRuntime; +use codex_utils_absolute_path::test_support::PathExt; use pretty_assertions::assert_eq; use std::path::Path; use tempfile::TempDir; @@ -32,8 +34,11 @@ async fn thread_delete_deletes_spawned_descendants() -> Result<()> { let grandchild_id = create_delete_test_rollout(codex_home.path(), /*minute*/ 2, "grandchild")?; - let state_db = - StateRuntime::init(codex_home.path().to_path_buf(), "mock_provider".into()).await?; + let state_db = StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()), + "mock_provider".into(), + ) + .await?; let parent_thread_id = ThreadId::from_string(&parent_id)?; let child_thread_id = ThreadId::from_string(&child_id)?; let grandchild_thread_id = ThreadId::from_string(&grandchild_id)?; @@ -139,8 +144,11 @@ async fn thread_delete_preflights_external_fork_references_for_spawned_subtrees( })?; std::fs::write(external_path.as_path(), format!("{external_meta}\n"))?; - let state_db = - StateRuntime::init(codex_home.path().to_path_buf(), "mock_provider".into()).await?; + let state_db = StateRuntime::init( + SqliteConfig::new_for_testing(codex_home.path().abs()), + "mock_provider".into(), + ) + .await?; state_db .upsert_thread_spawn_edge( parent_thread_id, diff --git a/codex-rs/app-server/tests/suite/v2/thread_fork.rs b/codex-rs/app-server/tests/suite/v2/thread_fork.rs index 2a7dba69c0d5..eb633a736e1e 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_fork.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_fork.rs @@ -46,6 +46,7 @@ use codex_rollout::append_rollout_item_to_path; use codex_rollout::append_thread_name; use codex_rollout::read_session_meta_line; use codex_state::StateRuntime; +use codex_utils_absolute_path::test_support::PathExt; use core_test_support::responses; use pretty_assertions::assert_eq; use serde_json::Value; @@ -522,8 +523,11 @@ async fn thread_fork_defers_inherited_active_goal_until_next_turn() -> Result<() } mcp.clear_message_buffer(); - let state_db = - StateRuntime::init(codex_home.path().to_path_buf(), "mock_provider".into()).await?; + let state_db = StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()), + "mock_provider".into(), + ) + .await?; let source_goal = state_db .thread_goals() .replace_thread_goal( diff --git a/codex-rs/app-server/tests/suite/v2/thread_list.rs b/codex-rs/app-server/tests/suite/v2/thread_list.rs index e17c44f56870..61d9f1c1fc1d 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_list.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_list.rs @@ -36,6 +36,7 @@ use codex_protocol::protocol::RolloutLine; use codex_protocol::protocol::SessionSource as CoreSessionSource; use codex_protocol::protocol::SubAgentSource; use codex_state::DirectionalThreadSpawnEdgeStatus; +use codex_utils_absolute_path::test_support::PathExt; use core_test_support::responses; use pretty_assertions::assert_eq; use std::cmp::Reverse; @@ -608,15 +609,17 @@ sqlite = true // `thread/list` applies `search_term` on the sqlite fast path. This test creates // rollouts manually, so mark the DB backfill complete and then run an unsearched // list large enough to repair every rollout the searched list should find. - let state_db = - codex_state::StateRuntime::init(codex_home.path().to_path_buf(), "mock_provider".into()) - .await?; + let state_db = codex_state::StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()), + "mock_provider".into(), + ) + .await?; state_db .mark_backfill_complete(/*last_watermark*/ None) .await?; let rollout_config = codex_rollout::RolloutConfig { codex_home: codex_home.path().to_path_buf(), - sqlite_home: codex_home.path().to_path_buf(), + sqlite: codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()), cwd: codex_home.path().to_path_buf(), model_provider_id: "mock_provider".to_string(), generate_memories: false, @@ -831,9 +834,11 @@ sqlite = true Some("mock_provider"), /*git_info*/ None, )?; - let state_db = - codex_state::StateRuntime::init(codex_home.path().to_path_buf(), "mock_provider".into()) - .await?; + let state_db = codex_state::StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()), + "mock_provider".into(), + ) + .await?; state_db .mark_backfill_complete(/*last_watermark*/ None) .await?; @@ -938,7 +943,7 @@ async fn thread_list_relation_filters_read_spawn_graph_from_state_db() -> Result let newer_child_id = ThreadId::new(); let grandchild_id = ThreadId::new(); let state_db = codex_state::StateRuntime::init( - codex_home.path().to_path_buf(), + codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()), "mock_provider".to_string(), ) .await?; @@ -1722,15 +1727,17 @@ async fn thread_list_sort_recency_at_uses_state_db_order_with_provider_filter() "2025-01-03T00:00:00Z", )?; - let state_db = - codex_state::StateRuntime::init(codex_home.path().to_path_buf(), "mock_provider".into()) - .await?; + let state_db = codex_state::StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()), + "mock_provider".into(), + ) + .await?; state_db .mark_backfill_complete(/*last_watermark*/ None) .await?; let rollout_config = codex_rollout::RolloutConfig { codex_home: codex_home.path().to_path_buf(), - sqlite_home: codex_home.path().to_path_buf(), + sqlite: codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()), cwd: codex_home.path().to_path_buf(), model_provider_id: "mock_provider".to_string(), generate_memories: false, diff --git a/codex-rs/app-server/tests/suite/v2/thread_memory_mode_set.rs b/codex-rs/app-server/tests/suite/v2/thread_memory_mode_set.rs index c43caaff120d..91988a9ed379 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_memory_mode_set.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_memory_mode_set.rs @@ -12,6 +12,7 @@ use codex_app_server_protocol::ThreadStartResponse; use codex_features::Feature; use codex_protocol::ThreadId; use codex_state::StateRuntime; +use codex_utils_absolute_path::test_support::PathExt; use pretty_assertions::assert_eq; use std::path::Path; use std::sync::Arc; @@ -99,7 +100,11 @@ async fn thread_memory_mode_set_updates_stored_thread_state() -> Result<()> { } async fn init_state_db(codex_home: &Path) -> Result> { - let state_db = StateRuntime::init(codex_home.to_path_buf(), "mock_provider".into()).await?; + let state_db = StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(codex_home.abs()), + "mock_provider".into(), + ) + .await?; state_db .mark_backfill_complete(/*last_watermark*/ None) .await?; diff --git a/codex-rs/app-server/tests/suite/v2/thread_metadata_update.rs b/codex-rs/app-server/tests/suite/v2/thread_metadata_update.rs index b754e0eaa886..2a51fb7f7f65 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_metadata_update.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_metadata_update.rs @@ -29,6 +29,7 @@ use codex_protocol::ThreadId; use codex_protocol::protocol::GitInfo as RolloutGitInfo; use codex_rollout::state_db::reconcile_rollout; use codex_state::StateRuntime; +use codex_utils_absolute_path::test_support::PathExt; use pretty_assertions::assert_eq; use serde_json::Value; use std::fs; @@ -681,7 +682,11 @@ async fn thread_metadata_update_can_clear_stored_git_fields() -> Result<()> { } async fn init_state_db(codex_home: &Path) -> Result> { - let state_db = StateRuntime::init(codex_home.to_path_buf(), "mock_provider".into()).await?; + let state_db = StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(codex_home.abs()), + "mock_provider".into(), + ) + .await?; state_db .mark_backfill_complete(/*last_watermark*/ None) .await?; diff --git a/codex-rs/app-server/tests/suite/v2/thread_read.rs b/codex-rs/app-server/tests/suite/v2/thread_read.rs index 645de1f8bc94..b3af8ccb3bf1 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_read.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_read.rs @@ -471,8 +471,7 @@ async fn thread_search_occurrences_reads_paginated_projection() -> Result<()> { let thread_id = codex_protocol::ThreadId::default(); let sqlite = codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()); let state_db = - codex_state::StateRuntime::init(sqlite.home().to_path_buf(), "mock_provider".to_string()) - .await?; + codex_state::StateRuntime::init(sqlite.clone(), "mock_provider".to_string()).await?; let store = LocalThreadStore::new( LocalThreadStoreConfig { codex_home: codex_home.path().to_path_buf(), @@ -1450,8 +1449,7 @@ async fn paginated_history_lists_use_projected_turns_and_items() -> Result<()> { let thread_id = codex_protocol::ThreadId::default(); let sqlite = codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()); let state_db = - codex_state::StateRuntime::init(sqlite.home().to_path_buf(), "mock_provider".to_string()) - .await?; + codex_state::StateRuntime::init(sqlite.clone(), "mock_provider".to_string()).await?; let store = LocalThreadStore::new( LocalThreadStoreConfig { codex_home: codex_home.path().to_path_buf(), diff --git a/codex-rs/app-server/tests/suite/v2/thread_resume.rs b/codex-rs/app-server/tests/suite/v2/thread_resume.rs index f348a8daf14e..d2105f56134f 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_resume.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_resume.rs @@ -98,6 +98,7 @@ use codex_rollout::append_rollout_item_to_path; use codex_rollout::read_session_meta_line; use codex_state::StateRuntime; use codex_utils_absolute_path::AbsolutePathBuf; +use codex_utils_absolute_path::test_support::PathExt; use codex_utils_path_uri::LegacyAppPathString; use core_test_support::responses; use core_test_support::skip_if_no_network; @@ -1890,8 +1891,11 @@ async fn thread_goal_set_edits_objective_without_resetting_usage() -> Result<()> ) .await??; - let state_db = - StateRuntime::init(codex_home.path().to_path_buf(), "mock_provider".into()).await?; + let state_db = StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()), + "mock_provider".into(), + ) + .await?; let thread_id = ThreadId::from_string(&thread_id)?; let thread_metadata = state_db .get_thread(thread_id) @@ -2589,8 +2593,11 @@ async fn thread_resume_prefers_persisted_git_metadata_for_local_threads() -> Res .join("\n") + "\n", )?; - let state_db = - StateRuntime::init(codex_home.path().to_path_buf(), "mock_provider".into()).await?; + let state_db = StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()), + "mock_provider".into(), + ) + .await?; state_db .mark_backfill_complete(/*last_watermark*/ None) .await?; diff --git a/codex-rs/cli/src/doctor.rs b/codex-rs/cli/src/doctor.rs index db9d21dad470..e3bdc6577825 100644 --- a/codex-rs/cli/src/doctor.rs +++ b/codex-rs/cli/src/doctor.rs @@ -1089,7 +1089,10 @@ fn config_check(config: &Config) -> DoctorCheck { )); details.push(format!("model provider: {}", config.model_provider_id)); details.push(format!("log dir: {}", config.log_dir.display())); - details.push(format!("sqlite home: {}", config.sqlite_home.display())); + details.push(format!( + "sqlite home: {}", + config.sqlite_config().home().display() + )); details.push(format!("mcp servers: {}", config.mcp_servers.get().len())); feature_flag_details(config, &mut details); config_toml_details(config, &mut details); @@ -2163,17 +2166,18 @@ async fn state_check(config: &Config) -> DoctorCheck { let mut details = Vec::new(); path_readiness(&mut details, "CODEX_HOME", &config.codex_home); path_readiness(&mut details, "log dir", &config.log_dir); - path_readiness(&mut details, "sqlite home", &config.sqlite_home); + path_readiness(&mut details, "sqlite home", config.sqlite_config().home()); let mut integrity_failures = Vec::new(); - let sqlite = codex_state::SqliteConfig::from_sqlite_home( - codex_utils_absolute_path::AbsolutePathBuf::resolve_path_against_base( - &config.sqlite_home, - &config.codex_home, - ), - ); - for db in sqlite.runtime_db_paths() { + for db in config.sqlite_config().runtime_db_paths() { path_readiness(&mut details, db.label, &db.path); - sqlite_integrity_detail(&mut details, &mut integrity_failures, db.label, &db.path).await; + sqlite_integrity_detail( + config.sqlite_config(), + &mut details, + &mut integrity_failures, + db.label, + &db.path, + ) + .await; } rollout_stats_details(&mut details, &config.codex_home); standalone_release_cache_details(&mut details); @@ -2198,6 +2202,7 @@ async fn state_check(config: &Config) -> DoctorCheck { } async fn sqlite_integrity_detail( + sqlite: &codex_state::SqliteConfig, details: &mut Vec, integrity_failures: &mut Vec, label: &str, @@ -2208,7 +2213,7 @@ async fn sqlite_integrity_detail( return; } - match codex_state::sqlite_integrity_check(path).await { + match codex_state::sqlite_integrity_check(sqlite, path).await { Ok(rows) if rows.iter().all(|row| row == "ok") => { details.push(format!("{label} integrity: ok")); } diff --git a/codex-rs/cli/src/doctor/thread_inventory.rs b/codex-rs/cli/src/doctor/thread_inventory.rs index 61fb6b74b611..8b220c9c680a 100644 --- a/codex-rs/cli/src/doctor/thread_inventory.rs +++ b/codex-rs/cli/src/doctor/thread_inventory.rs @@ -91,15 +91,9 @@ impl RolloutScan { } pub(super) async fn thread_inventory_check(config: &Config) -> DoctorCheck { - let sqlite = codex_state::SqliteConfig::from_sqlite_home( - codex_utils_absolute_path::AbsolutePathBuf::resolve_path_against_base( - &config.sqlite_home, - &config.codex_home, - ), - ); thread_inventory_check_for_roots( config.codex_home.as_path(), - &sqlite, + config.sqlite_config(), config.model_provider_id.as_str(), ) .await @@ -142,7 +136,7 @@ async fn thread_inventory_check_for_roots( return missing_state_db_check(scan, details); } - let rows = match codex_state::read_thread_state_audit_rows(&state_db_path).await { + let rows = match codex_state::read_thread_state_audit_rows(sqlite).await { Ok(rows) => rows, Err(err) => { details.push(format!("rollout DB read error: {err}")); @@ -1310,7 +1304,7 @@ mod tests { let codex_home = TempDir::new().expect("codex home"); let sqlite_home = TempDir::new().expect("sqlite home"); let _runtime = codex_state::StateRuntime::init( - sqlite_home.path().to_path_buf(), + codex_state::SqliteConfig::new_for_testing(sqlite_home.path().abs()), "test-provider".to_string(), ) .await @@ -1359,9 +1353,8 @@ mod tests { async fn insert_thread_row(&self, id: &str, rollout_path: &Path, archived: bool) { let sqlite = self.sqlite(); - let state_db_path = sqlite.state_db_path(); let pool = sqlite - .open_read_write_pool(&state_db_path) + .open_read_write_pool(&sqlite.state_db_path()) .await .expect("sqlite pool"); sqlx::query( diff --git a/codex-rs/cli/src/main.rs b/codex-rs/cli/src/main.rs index 62f158f7602a..f8b868d55d46 100644 --- a/codex-rs/cli/src/main.rs +++ b/codex-rs/cli/src/main.rs @@ -26,7 +26,6 @@ use codex_execpolicy::ExecPolicyCheckCommand; use codex_responses_api_proxy::Args as ResponsesApiProxyArgs; use codex_rollout_trace::REDUCED_STATE_FILE_NAME; use codex_rollout_trace::replay_bundle; -use codex_state::SqliteConfig; use codex_state::StateRuntime; use codex_tui::AppExitInfo; use codex_tui::Cli as TuiCli; @@ -2062,11 +2061,9 @@ async fn run_debug_clear_memories_command( .build() .await?; - let sqlite_home = - AbsolutePathBuf::resolve_path_against_base(&config.sqlite_home, &config.codex_home); - let memories_path = SqliteConfig::from_sqlite_home(sqlite_home).memories_db_path(); + let memories_path = config.sqlite_config().memories_db_path(); let cleared_memories_db = - StateRuntime::clear_memory_data_in_sqlite_home(config.sqlite_home.as_path()).await?; + StateRuntime::clear_memory_data_in_sqlite_home(config.sqlite_config()).await?; clear_memory_roots_contents(&config.codex_home).await?; diff --git a/codex-rs/cli/tests/debug_clear_memories.rs b/codex-rs/cli/tests/debug_clear_memories.rs index 1266ddb60c22..449825219d1d 100644 --- a/codex-rs/cli/tests/debug_clear_memories.rs +++ b/codex-rs/cli/tests/debug_clear_memories.rs @@ -16,8 +16,7 @@ fn codex_command(codex_home: &Path) -> Result { async fn debug_clear_memories_resets_state_and_removes_memory_dir() -> Result<()> { let codex_home = TempDir::new()?; let sqlite = codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()); - let runtime = - StateRuntime::init(codex_home.path().to_path_buf(), "test-provider".to_string()).await?; + let runtime = StateRuntime::init(sqlite.clone(), "test-provider".to_string()).await?; drop(runtime); let thread_id = "00000000-0000-0000-0000-000000000123"; @@ -139,8 +138,7 @@ INSERT INTO jobs ( async fn debug_clear_memories_resets_memories_db_without_state_db() -> Result<()> { let codex_home = TempDir::new()?; let sqlite = codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()); - let runtime = - StateRuntime::init(codex_home.path().to_path_buf(), "test-provider".to_string()).await?; + let runtime = StateRuntime::init(sqlite.clone(), "test-provider".to_string()).await?; runtime.close().await; let db_path = sqlite.state_db_path(); diff --git a/codex-rs/core-api/Cargo.toml b/codex-rs/core-api/Cargo.toml index 2a5913d1a8cd..a6f061eadf67 100644 --- a/codex-rs/core-api/Cargo.toml +++ b/codex-rs/core-api/Cargo.toml @@ -28,4 +28,5 @@ codex-login = { workspace = true } codex-model-provider-info = { workspace = true } codex-models-manager = { workspace = true } codex-protocol = { workspace = true } +codex-state = { workspace = true } codex-utils-absolute-path = { workspace = true } diff --git a/codex-rs/core-api/src/lib.rs b/codex-rs/core-api/src/lib.rs index 865e50fbc19d..3824f3558954 100644 --- a/codex-rs/core-api/src/lib.rs +++ b/codex-rs/core-api/src/lib.rs @@ -108,4 +108,5 @@ pub use codex_protocol::protocol::SessionSource; pub use codex_protocol::protocol::TurnEnvironmentSelection; pub use codex_protocol::protocol::W3cTraceContext; pub use codex_protocol::user_input::UserInput; +pub use codex_state::SqliteConfig; pub use codex_utils_absolute_path::AbsolutePathBuf; diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index 9103ed8440c2..3b4bb448c293 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -5414,7 +5414,7 @@ async fn sqlite_home_defaults_to_codex_home_for_workspace_write() -> std::io::Re ) .await?; - assert_eq!(config.sqlite_home, codex_home.path().to_path_buf()); + assert_eq!(config.sqlite.home(), codex_home.path()); Ok(()) } @@ -11933,7 +11933,7 @@ sandbox_private_desktop = false ); let config = load_with_enterprise_requirement(&codex_home, requirements).await?; - assert_eq!(config.sqlite_home, required_sqlite_home); + assert_eq!(config.sqlite.home(), required_sqlite_home.as_path()); assert_eq!(config.log_dir, required_log_dir); assert_eq!(config.model_catalog, Some(catalog)); assert!(!config.check_for_update_on_startup); diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index ab7cc5092b26..e7422d5a5bf7 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -272,18 +272,16 @@ const LOCAL_DEV_BUILD_VERSION: &str = "0.0.0"; pub const CONFIG_TOML_FILE: &str = "config.toml"; const CONFIG_PROFILE_V2_SUFFIX: &str = ".config.toml"; -fn resolve_sqlite_home_env(resolved_cwd: &Path) -> Option { +fn resolve_sqlite_home_env(resolved_cwd: &Path) -> Option { let raw = std::env::var(codex_state::SQLITE_HOME_ENV).ok()?; let trimmed = raw.trim(); if trimmed.is_empty() { return None; } - let path = PathBuf::from(trimmed); - if path.is_absolute() { - Some(path) - } else { - Some(resolved_cwd.join(path)) - } + Some(AbsolutePathBuf::resolve_path_against_base( + trimmed, + resolved_cwd, + )) } fn resolve_cli_auth_credentials_store_mode( @@ -893,8 +891,8 @@ pub struct Config { /// overridden by the `CODEX_HOME` environment variable). pub codex_home: AbsolutePathBuf, - /// Directory where Codex stores the SQLite state DB. - pub sqlite_home: PathBuf, + /// Resolved configuration shared by all Codex SQLite databases. + pub sqlite: codex_state::SqliteConfig, /// Directory where Codex writes log files (defaults to `$CODEX_HOME/log`). pub log_dir: PathBuf, @@ -1437,6 +1435,10 @@ impl ConfigBuilder { } impl Config { + pub fn sqlite_config(&self) -> &codex_state::SqliteConfig { + &self.sqlite + } + pub(crate) fn multi_agent_version_override(&self) -> Option { if self.features.enabled(Feature::MultiAgentV2) { Some(MultiAgentVersion::V2) @@ -3822,9 +3824,9 @@ impl Config { let sqlite_home = cfg .sqlite_home .as_ref() - .map(AbsolutePathBuf::to_path_buf) + .cloned() .or(sqlite_home_env) - .unwrap_or_else(|| codex_home.to_path_buf()); + .unwrap_or_else(|| codex_home.clone()); let original_permission_profile = permission_profile.clone(); apply_requirement_constrained_value( "approval_policy", @@ -4019,7 +4021,7 @@ impl Config { memories: memories_config, agent_interrupt_message_enabled, codex_home, - sqlite_home, + sqlite: codex_state::SqliteConfig::from_sqlite_home(sqlite_home), log_dir, config_lock_export_dir: cfg .debug diff --git a/codex-rs/core/src/rollout.rs b/codex-rs/core/src/rollout.rs index 73fbe5b5e42e..ca1dcdeb17e5 100644 --- a/codex-rs/core/src/rollout.rs +++ b/codex-rs/core/src/rollout.rs @@ -28,8 +28,8 @@ impl codex_rollout::RolloutConfigView for Config { self.codex_home.as_path() } - fn sqlite_home(&self) -> &std::path::Path { - self.sqlite_home.as_path() + fn sqlite_config(&self) -> &codex_state::SqliteConfig { + self.sqlite_config() } fn cwd(&self) -> &std::path::Path { diff --git a/codex-rs/core/src/session/tests.rs b/codex-rs/core/src/session/tests.rs index 89fdd0568092..0cc73231e695 100644 --- a/codex-rs/core/src/session/tests.rs +++ b/codex-rs/core/src/session/tests.rs @@ -5776,7 +5776,7 @@ async fn make_session_with_history_source_and_agent_control_and_rx( codex_thread_store::LocalThreadStoreConfig::from_config(config.as_ref()), Some( codex_state::StateRuntime::init( - config.sqlite_home.clone(), + config.sqlite.clone(), config.model_provider_id.clone(), ) .await diff --git a/codex-rs/core/tests/suite/rollout_list_find.rs b/codex-rs/core/tests/suite/rollout_list_find.rs index c8b10b24e8a3..771f370fc1ee 100644 --- a/codex-rs/core/tests/suite/rollout_list_find.rs +++ b/codex-rs/core/tests/suite/rollout_list_find.rs @@ -1,4 +1,5 @@ #![allow(clippy::unwrap_used)] +use codex_utils_absolute_path::test_support::PathExt; use std::io::Write; use std::path::Path; use std::path::PathBuf; @@ -66,9 +67,12 @@ async fn upsert_thread_metadata( thread_id: ThreadId, rollout_path: PathBuf, ) -> StateDbHandle { - let runtime = StateRuntime::init(codex_home.to_path_buf(), "test-provider".to_string()) - .await - .unwrap(); + let runtime = StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(codex_home.abs()), + "test-provider".to_string(), + ) + .await + .unwrap(); runtime .mark_backfill_complete(/*last_watermark*/ None) .await diff --git a/codex-rs/core/tests/suite/sqlite_state.rs b/codex-rs/core/tests/suite/sqlite_state.rs index b29ac524b106..35c65a433cac 100644 --- a/codex-rs/core/tests/suite/sqlite_state.rs +++ b/codex-rs/core/tests/suite/sqlite_state.rs @@ -24,7 +24,6 @@ use codex_protocol::protocol::SessionSource; use codex_protocol::protocol::UserMessageEvent; use codex_protocol::user_input::UserInput; use codex_web_search_extension::install as install_web_search_extension; -use core_test_support::PathExt; use core_test_support::responses; use core_test_support::responses::ev_completed; use core_test_support::responses::ev_function_call; @@ -67,9 +66,7 @@ async fn new_thread_is_recorded_in_state_db() -> Result<()> { let thread_id = test.session_configured.thread_id; let rollout_path = test.codex.rollout_path().expect("rollout path"); - let db_path = - codex_state::SqliteConfig::new_for_testing(test.config.sqlite_home.as_path().abs()) - .state_db_path(); + let db_path = test.config.sqlite.state_db_path(); for _ in 0..100 { if tokio::fs::try_exists(&db_path).await.unwrap_or(false) { @@ -426,9 +423,7 @@ async fn backfill_scans_existing_rollouts() -> Result<()> { let test = builder.build(&server).await?; - let db_path = - codex_state::SqliteConfig::new_for_testing(test.config.sqlite_home.as_path().abs()) - .state_db_path(); + let db_path = test.config.sqlite.state_db_path(); let rollout_path = test.config.codex_home.join(&rollout_rel_path); let default_provider = test.config.model_provider_id.clone(); @@ -479,9 +474,7 @@ async fn user_messages_persist_in_state_db() -> Result<()> { }); let test = builder.build(&server).await?; - let db_path = - codex_state::SqliteConfig::new_for_testing(test.config.sqlite_home.as_path().abs()) - .state_db_path(); + let db_path = test.config.sqlite.state_db_path(); for _ in 0..100 { if tokio::fs::try_exists(&db_path).await.unwrap_or(false) { break; diff --git a/codex-rs/ext/goal/Cargo.toml b/codex-rs/ext/goal/Cargo.toml index 4d75ba2f26ae..f6ee92ce3aef 100644 --- a/codex-rs/ext/goal/Cargo.toml +++ b/codex-rs/ext/goal/Cargo.toml @@ -30,6 +30,7 @@ tracing = { workspace = true } [dev-dependencies] anyhow = { workspace = true } chrono = { workspace = true } +codex-utils-absolute-path = { workspace = true } pretty_assertions = { workspace = true } tempfile = { workspace = true } tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } diff --git a/codex-rs/ext/goal/tests/goal_extension_backend.rs b/codex-rs/ext/goal/tests/goal_extension_backend.rs index 206216058067..eb751321df6e 100644 --- a/codex-rs/ext/goal/tests/goal_extension_backend.rs +++ b/codex-rs/ext/goal/tests/goal_extension_backend.rs @@ -1,6 +1,7 @@ #![recursion_limit = "256"] #![allow(clippy::expect_used)] +use codex_utils_absolute_path::test_support::PathExt; use std::sync::Arc; use std::sync::Mutex; use std::sync::PoisonError; @@ -1358,7 +1359,11 @@ fn tool_call(tool_name: &str, call_id: &str, arguments: serde_json::Value) -> To async fn test_runtime() -> anyhow::Result> { let tempdir = TempDir::new()?; - codex_state::StateRuntime::init(tempdir.keep(), "test-provider".to_string()).await + codex_state::StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(tempdir.keep().as_path().abs()), + "test-provider".to_string(), + ) + .await } fn test_thread_id() -> anyhow::Result { @@ -1372,7 +1377,8 @@ async fn seed_thread_metadata( let builder = codex_state::ThreadMetadataBuilder::new( thread_id, runtime - .codex_home() + .sqlite() + .home() .join(format!("rollout-{thread_id}.jsonl")), chrono::Utc::now(), SessionSource::Cli, diff --git a/codex-rs/memories/write/src/startup_tests.rs b/codex-rs/memories/write/src/startup_tests.rs index 2e11e40a0304..f53859801bc8 100644 --- a/codex-rs/memories/write/src/startup_tests.rs +++ b/codex-rs/memories/write/src/startup_tests.rs @@ -30,6 +30,7 @@ use codex_protocol::protocol::RolloutItem; use codex_protocol::protocol::RolloutLine; use codex_protocol::protocol::SessionSource; use codex_state::Phase2JobClaimOutcome; +use codex_utils_absolute_path::test_support::PathExt; use core_test_support::responses::ResponseMock; use core_test_support::responses::ResponsesRequest; use core_test_support::responses::ev_assistant_message; @@ -596,8 +597,11 @@ async fn build_test_codex_with_memories_config( } async fn init_state_db(home: &Arc) -> anyhow::Result> { - let db = - codex_state::StateRuntime::init(home.path().to_path_buf(), "test-provider".into()).await?; + let db = codex_state::StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(home.path().abs()), + "test-provider".into(), + ) + .await?; db.mark_backfill_complete(/*last_watermark*/ None).await?; Ok(db) } diff --git a/codex-rs/rollout/Cargo.toml b/codex-rs/rollout/Cargo.toml index da257fbcecbd..13d379c117d0 100644 --- a/codex-rs/rollout/Cargo.toml +++ b/codex-rs/rollout/Cargo.toml @@ -47,4 +47,5 @@ uuid = { workspace = true } zstd = { workspace = true } [dev-dependencies] +codex-utils-absolute-path = { workspace = true } pretty_assertions = { workspace = true } diff --git a/codex-rs/rollout/src/compression_tests.rs b/codex-rs/rollout/src/compression_tests.rs index f5c74a353e40..8b54ae5ec852 100644 --- a/codex-rs/rollout/src/compression_tests.rs +++ b/codex-rs/rollout/src/compression_tests.rs @@ -1,3 +1,4 @@ +use codex_utils_absolute_path::test_support::PathExt; use std::fs; use std::fs::FileTimes; #[cfg(unix)] @@ -392,7 +393,7 @@ async fn resume_materializes_compressed_rollout_path() -> anyhow::Result<()> { let home = TempDir::new()?; let config = RolloutConfig { codex_home: home.path().to_path_buf(), - sqlite_home: home.path().to_path_buf(), + sqlite: codex_state::SqliteConfig::new_for_testing(home.path().abs()), cwd: home.path().to_path_buf(), model_provider_id: "test-provider".to_string(), generate_memories: true, diff --git a/codex-rs/rollout/src/config.rs b/codex-rs/rollout/src/config.rs index 420949bfbd31..59d2b6453b2c 100644 --- a/codex-rs/rollout/src/config.rs +++ b/codex-rs/rollout/src/config.rs @@ -1,10 +1,11 @@ +use codex_state::SqliteConfig; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; pub trait RolloutConfigView { fn codex_home(&self) -> &Path; - fn sqlite_home(&self) -> &Path; + fn sqlite_config(&self) -> &SqliteConfig; fn cwd(&self) -> &Path; fn model_provider_id(&self) -> &str; fn generate_memories(&self) -> bool; @@ -13,7 +14,7 @@ pub trait RolloutConfigView { #[derive(Clone, Debug, PartialEq, Eq)] pub struct RolloutConfig { pub codex_home: PathBuf, - pub sqlite_home: PathBuf, + pub sqlite: SqliteConfig, pub cwd: PathBuf, pub model_provider_id: String, pub generate_memories: bool, @@ -25,7 +26,7 @@ impl RolloutConfig { pub fn from_view(view: &impl RolloutConfigView) -> Self { Self { codex_home: view.codex_home().to_path_buf(), - sqlite_home: view.sqlite_home().to_path_buf(), + sqlite: view.sqlite_config().clone(), cwd: view.cwd().to_path_buf(), model_provider_id: view.model_provider_id().to_string(), generate_memories: view.generate_memories(), @@ -38,8 +39,8 @@ impl RolloutConfigView for RolloutConfig { self.codex_home.as_path() } - fn sqlite_home(&self) -> &Path { - self.sqlite_home.as_path() + fn sqlite_config(&self) -> &SqliteConfig { + &self.sqlite } fn cwd(&self) -> &Path { @@ -60,8 +61,8 @@ impl RolloutConfigView for &T { (*self).codex_home() } - fn sqlite_home(&self) -> &Path { - (*self).sqlite_home() + fn sqlite_config(&self) -> &SqliteConfig { + (*self).sqlite_config() } fn cwd(&self) -> &Path { @@ -82,8 +83,8 @@ impl RolloutConfigView for Arc { self.as_ref().codex_home() } - fn sqlite_home(&self) -> &Path { - self.as_ref().sqlite_home() + fn sqlite_config(&self) -> &SqliteConfig { + self.as_ref().sqlite_config() } fn cwd(&self) -> &Path { diff --git a/codex-rs/rollout/src/metadata_tests.rs b/codex-rs/rollout/src/metadata_tests.rs index 03677a8b56b1..cce72a642d9b 100644 --- a/codex-rs/rollout/src/metadata_tests.rs +++ b/codex-rs/rollout/src/metadata_tests.rs @@ -16,6 +16,7 @@ use codex_protocol::protocol::SessionSource; use codex_protocol::protocol::ThreadHistoryMode; use codex_state::BackfillStatus; use codex_state::ThreadMetadataBuilder; +use codex_utils_absolute_path::test_support::PathExt; use pretty_assertions::assert_eq; use std::fs::File; use std::io::Write; @@ -249,9 +250,12 @@ async fn backfill_sessions_resumes_from_watermark_and_marks_complete() { /*git*/ None, ); - let runtime = codex_state::StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = codex_state::StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let first_watermark = backfill_watermark_for_path(codex_home.as_path(), first_path.as_path()); runtime.mark_backfill_running().await.expect("mark running"); runtime @@ -314,9 +318,12 @@ async fn backfill_sessions_preserves_existing_git_branch_and_fills_missing_git_f }), ); - let runtime = codex_state::StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = codex_state::StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let thread_id = ThreadId::from_string(&thread_uuid.to_string()).expect("thread id"); let mut existing = extract_metadata_from_rollout(&rollout_path, "test-provider") .await @@ -360,9 +367,12 @@ async fn backfill_sessions_preserves_existing_paginated_memory_mode() { ThreadHistoryMode::Paginated, ); - let runtime = codex_state::StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = codex_state::StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let thread_id = ThreadId::from_string(&thread_uuid.to_string()).expect("thread id"); let existing = extract_metadata_from_rollout(&rollout_path, "test-provider") .await @@ -407,9 +417,12 @@ async fn backfill_sessions_normalizes_cwd_before_upsert() { ThreadHistoryMode::Legacy, ); - let runtime = codex_state::StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = codex_state::StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); backfill_sessions(runtime.as_ref(), codex_home.as_path(), "test-provider").await; diff --git a/codex-rs/rollout/src/recorder.rs b/codex-rs/rollout/src/recorder.rs index 7b6d783fc96c..81ee9820f2b9 100644 --- a/codex-rs/rollout/src/recorder.rs +++ b/codex-rs/rollout/src/recorder.rs @@ -437,6 +437,7 @@ impl RolloutRecorder { search_term: Option<&str>, ) -> std::io::Result { let codex_home = config.codex_home(); + let sqlite = config.sqlite_config(); let archived = match archive_filter { ThreadListArchiveFilter::Active => false, ThreadListArchiveFilter::Archived => true, @@ -448,7 +449,7 @@ impl RolloutRecorder { if matches!(repair_mode, ThreadListRepairMode::StateDbOnly) { return Ok(state_db::list_threads_db( state_db_ctx.as_deref(), - codex_home, + sqlite, page_size, cursor, sort_key, @@ -558,7 +559,7 @@ impl RolloutRecorder { let db_page = state_db::list_threads_db( state_db_ctx.as_deref(), - codex_home, + sqlite, page_size, cursor, sort_key, @@ -588,7 +589,7 @@ impl RolloutRecorder { } if let Some(repaired_db_page) = state_db::list_threads_db( state_db_ctx.as_deref(), - codex_home, + sqlite, page_size, cursor, sort_key, @@ -629,7 +630,7 @@ impl RolloutRecorder { if sort_key == ThreadSortKey::RecencyAt { if let Some(repaired_db_page) = state_db::list_threads_db( state_db_ctx.as_deref(), - codex_home, + sqlite, page_size, cursor, sort_key, @@ -701,6 +702,7 @@ impl RolloutRecorder { filter_cwd: Option<&Path>, ) -> std::io::Result> { let codex_home = config.codex_home(); + let sqlite = config.sqlite_config(); let cwd_filter = filter_cwd.map(Path::to_path_buf); let mut fallback_reason = state_db_ctx.is_none().then_some("db_unavailable"); if state_db_ctx.is_some() { @@ -708,7 +710,7 @@ impl RolloutRecorder { loop { let Some(db_page) = state_db::list_threads_db( state_db_ctx.as_deref(), - codex_home, + sqlite, page_size, db_cursor.as_ref(), sort_key, diff --git a/codex-rs/rollout/src/recorder_tests.rs b/codex-rs/rollout/src/recorder_tests.rs index 248e9c218238..7ba7143b410c 100644 --- a/codex-rs/rollout/src/recorder_tests.rs +++ b/codex-rs/rollout/src/recorder_tests.rs @@ -18,6 +18,7 @@ use codex_protocol::protocol::SessionSource; use codex_protocol::protocol::ThreadHistoryMode; use codex_protocol::protocol::TurnContextItem; use codex_protocol::protocol::UserMessageEvent; +use codex_utils_absolute_path::test_support::PathExt; use pretty_assertions::assert_eq; use std::fs; use std::fs::File; @@ -31,7 +32,7 @@ use uuid::Uuid; fn test_config(codex_home: &Path) -> RolloutConfig { RolloutConfig { codex_home: codex_home.to_path_buf(), - sqlite_home: codex_home.to_path_buf(), + sqlite: codex_state::SqliteConfig::new_for_testing(codex_home.abs()), cwd: codex_home.to_path_buf(), model_provider_id: "test-provider".to_string(), generate_memories: true, @@ -943,7 +944,7 @@ async fn list_threads_db_enabled_drops_missing_rollout_paths() -> std::io::Resul )); let runtime = codex_state::StateRuntime::init( - home.path().to_path_buf(), + codex_state::SqliteConfig::new_for_testing(home.path().abs()), config.model_provider_id.clone(), ) .await @@ -1009,7 +1010,7 @@ async fn list_threads_db_enabled_repairs_stale_rollout_paths() -> std::io::Resul )); let runtime = codex_state::StateRuntime::init( - home.path().to_path_buf(), + codex_state::SqliteConfig::new_for_testing(home.path().abs()), config.model_provider_id.clone(), ) .await @@ -1070,7 +1071,7 @@ async fn list_threads_state_db_only_skips_jsonl_repair_scan() -> std::io::Result let config = test_config(home.path()); let runtime = codex_state::StateRuntime::init( - home.path().to_path_buf(), + codex_state::SqliteConfig::new_for_testing(home.path().abs()), config.model_provider_id.clone(), ) .await @@ -1174,7 +1175,7 @@ async fn list_threads_default_filter_returns_filesystem_scan_results() -> std::i let stale_cwd = home.path().join("stale-cwd"); let runtime = codex_state::StateRuntime::init( - home.path().to_path_buf(), + codex_state::SqliteConfig::new_for_testing(home.path().abs()), config.model_provider_id.clone(), ) .await @@ -1264,7 +1265,7 @@ async fn list_threads_metadata_filter_overlays_state_db_list_metadata() -> std:: let rollout_path = write_session_file(home.path(), "2025-01-03T16-00-00", uuid)?; let runtime = codex_state::StateRuntime::init( - home.path().to_path_buf(), + codex_state::SqliteConfig::new_for_testing(home.path().abs()), config.model_provider_id.clone(), ) .await @@ -1407,7 +1408,7 @@ async fn list_threads_search_repairs_stale_state_db_hits_before_returning() -> s let real_path = write_session_file(home.path(), "2025-01-03T15-00-00", uuid)?; let runtime = codex_state::StateRuntime::init( - home.path().to_path_buf(), + codex_state::SqliteConfig::new_for_testing(home.path().abs()), config.model_provider_id.clone(), ) .await diff --git a/codex-rs/rollout/src/state_db.rs b/codex-rs/rollout/src/state_db.rs index 916c75a32557..e26b15fd2583 100644 --- a/codex-rs/rollout/src/state_db.rs +++ b/codex-rs/rollout/src/state_db.rs @@ -13,8 +13,8 @@ use codex_protocol::protocol::RolloutItem; use codex_protocol::protocol::SessionSource; use codex_protocol::protocol::ThreadHistoryMode; pub use codex_state::LogEntry; +use codex_state::SqliteConfig; use codex_state::ThreadMetadataBuilder; -use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_path::normalize_for_path_comparison; use serde_json::Value; use std::path::Path; @@ -44,13 +44,7 @@ const STARTUP_BACKFILL_WAIT_TIMEOUT: Duration = Duration::from_secs(2); /// initialized handle. pub async fn init(config: &impl RolloutConfigView) -> Option { let config = RolloutConfig::from_view(config); - match try_init_with_roots( - config.codex_home, - config.sqlite_home, - config.model_provider_id, - ) - .await - { + match try_init_with_roots(config.codex_home, config.sqlite, config.model_provider_id).await { Ok(runtime) => Some(runtime), Err(err) => { emit_startup_warning(&format!("failed to initialize state runtime: {err:#}")); @@ -65,22 +59,17 @@ pub async fn init(config: &impl RolloutConfigView) -> Option { /// tracing or UI setup has completed. pub async fn try_init(config: &impl RolloutConfigView) -> anyhow::Result { let config = RolloutConfig::from_view(config); - try_init_with_roots( - config.codex_home, - config.sqlite_home, - config.model_provider_id, - ) - .await + try_init_with_roots(config.codex_home, config.sqlite, config.model_provider_id).await } async fn try_init_with_roots( codex_home: PathBuf, - sqlite_home: PathBuf, + sqlite: SqliteConfig, default_model_provider_id: String, ) -> anyhow::Result { try_init_with_roots_inner( codex_home, - sqlite_home, + sqlite, default_model_provider_id, /*backfill_lease_seconds*/ None, ) @@ -90,13 +79,13 @@ async fn try_init_with_roots( #[cfg(test)] async fn try_init_with_roots_and_backfill_lease( codex_home: PathBuf, - sqlite_home: PathBuf, + sqlite: SqliteConfig, default_model_provider_id: String, backfill_lease_seconds: i64, ) -> anyhow::Result { try_init_with_roots_inner( codex_home, - sqlite_home, + sqlite, default_model_provider_id, Some(backfill_lease_seconds), ) @@ -105,17 +94,17 @@ async fn try_init_with_roots_and_backfill_lease( async fn try_init_with_roots_inner( codex_home: PathBuf, - sqlite_home: PathBuf, + sqlite: SqliteConfig, default_model_provider_id: String, backfill_lease_seconds: Option, ) -> anyhow::Result { let runtime = - codex_state::StateRuntime::init(sqlite_home.clone(), default_model_provider_id.clone()) + codex_state::StateRuntime::init(sqlite.clone(), default_model_provider_id.clone()) .await .with_context(|| { format!( "failed to initialize state runtime at {}", - sqlite_home.display() + sqlite.home().display() ) })?; let backfill_gate_started = Instant::now(); @@ -217,8 +206,7 @@ fn emit_startup_warning(message: &str) { /// Unlike [`init`], this helper does not run rollout backfill. It is for /// optional local reads from non-owning contexts such as remote app-server mode. pub async fn get_state_db(config: &impl RolloutConfigView) -> Option { - let sqlite_home = AbsolutePathBuf::try_from(config.sqlite_home()).ok()?; - let state_path = codex_state::SqliteConfig::from_sqlite_home(sqlite_home).state_db_path(); + let state_path = config.sqlite_config().state_db_path(); if !tokio::fs::try_exists(&state_path).await.unwrap_or(false) { codex_state::record_fallback( "get_state_db", @@ -228,7 +216,7 @@ pub async fn get_state_db(config: &impl RolloutConfigView) -> Option Option PathBuf { #[allow(clippy::too_many_arguments)] pub async fn list_thread_ids_db( context: Option<&codex_state::StateRuntime>, - codex_home: &Path, + sqlite: &codex_state::SqliteConfig, page_size: usize, cursor: Option<&Cursor>, sort_key: ThreadSortKey, @@ -317,12 +305,13 @@ pub async fn list_thread_ids_db( stage: &str, ) -> Option> { let ctx = context?; - if ctx.codex_home() != codex_home { + if ctx.sqlite() != sqlite { warn!( - "state db codex_home mismatch: expected {}, got {}", - ctx.codex_home().display(), - codex_home.display() + "state db SQLite home mismatch: expected {}, got {}", + sqlite.home().display(), + ctx.sqlite().home().display() ); + return None; } let anchor = cursor_to_anchor(cursor); @@ -362,7 +351,7 @@ pub async fn list_thread_ids_db( #[allow(clippy::too_many_arguments)] pub async fn list_threads_db( context: Option<&codex_state::StateRuntime>, - codex_home: &Path, + sqlite: &codex_state::SqliteConfig, page_size: usize, cursor: Option<&Cursor>, sort_key: ThreadSortKey, @@ -376,12 +365,13 @@ pub async fn list_threads_db( search_term: Option<&str>, ) -> Option { let ctx = context?; - if ctx.codex_home() != codex_home { + if ctx.sqlite() != sqlite { warn!( - "state db codex_home mismatch: expected {}, got {}", - ctx.codex_home().display(), - codex_home.display() + "state db SQLite home mismatch: expected {}, got {}", + sqlite.home().display(), + ctx.sqlite().home().display() ); + return None; } let anchor = cursor_to_anchor(cursor); diff --git a/codex-rs/rollout/src/state_db_tests.rs b/codex-rs/rollout/src/state_db_tests.rs index 5cc22fa65427..d77b68941f9d 100644 --- a/codex-rs/rollout/src/state_db_tests.rs +++ b/codex-rs/rollout/src/state_db_tests.rs @@ -12,6 +12,7 @@ use codex_protocol::protocol::SessionMeta; use codex_protocol::protocol::SessionMetaLine; use codex_protocol::protocol::ThreadHistoryMode; use codex_protocol::protocol::UserMessageEvent; +use codex_utils_absolute_path::test_support::PathExt; use pretty_assertions::assert_eq; use std::path::Path; use tempfile::TempDir; @@ -47,12 +48,59 @@ fn cursor_to_anchor_preserves_recency_tie_breaker() { ); } +/// A runtime for another SQLite home must not be queried or clean up rows when +/// a caller supplies a mismatched configuration. +#[tokio::test] +async fn list_threads_db_rejects_mismatched_sqlite_config_without_cleanup() -> anyhow::Result<()> { + let root = TempDir::new().expect("temp dir"); + let runtime_sqlite = codex_state::SqliteConfig::new_for_testing( + root.path().join("runtime-sqlite").as_path().abs(), + ); + let requested_sqlite = codex_state::SqliteConfig::new_for_testing( + root.path().join("requested-sqlite").as_path().abs(), + ); + let runtime = + codex_state::StateRuntime::init(runtime_sqlite, "test-provider".to_string()).await?; + let thread_id = ThreadId::new(); + let metadata = ThreadMetadataBuilder::new( + thread_id, + root.path().join("missing-rollout.jsonl"), + Utc::now(), + SessionSource::Cli, + ) + .build("test-provider"); + runtime.upsert_thread(&metadata).await?; + + let page = list_threads_db( + Some(runtime.as_ref()), + &requested_sqlite, + /*page_size*/ 10, + /*cursor*/ None, + ThreadSortKey::CreatedAt, + SortDirection::Desc, + &[], + /*model_providers*/ None, + /*cwd_filters*/ None, + /*relation_filter*/ None, + /*archived*/ false, + /*is_pinned*/ None, + /*search_term*/ None, + ) + .await; + + assert!(page.is_none()); + assert_eq!(runtime.get_thread(thread_id).await?, Some(metadata)); + Ok(()) +} + #[tokio::test] async fn try_init_waits_for_concurrent_startup_backfill() -> anyhow::Result<()> { let home = TempDir::new().expect("temp dir"); - let runtime = - codex_state::StateRuntime::init(home.path().to_path_buf(), "test-provider".to_string()) - .await?; + let runtime = codex_state::StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(home.path().abs()), + "test-provider".to_string(), + ) + .await?; let claimed = runtime.try_claim_backfill(/*lease_seconds*/ 60).await?; assert!(claimed); let runtime_for_completion = runtime.clone(); @@ -65,7 +113,7 @@ async fn try_init_waits_for_concurrent_startup_backfill() -> anyhow::Result<()> let initialized = try_init_with_roots_and_backfill_lease( home.path().to_path_buf(), - home.path().to_path_buf(), + codex_state::SqliteConfig::new_for_testing(home.path().abs()), "test-provider".to_string(), /*backfill_lease_seconds*/ 60, ) @@ -82,15 +130,17 @@ async fn try_init_waits_for_concurrent_startup_backfill() -> anyhow::Result<()> #[tokio::test] async fn try_init_times_out_waiting_for_stuck_startup_backfill() -> anyhow::Result<()> { let home = TempDir::new().expect("temp dir"); - let runtime = - codex_state::StateRuntime::init(home.path().to_path_buf(), "test-provider".to_string()) - .await?; + let runtime = codex_state::StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(home.path().abs()), + "test-provider".to_string(), + ) + .await?; let claimed = runtime.try_claim_backfill(/*lease_seconds*/ 60).await?; assert!(claimed); let result = try_init_with_roots_and_backfill_lease( home.path().to_path_buf(), - home.path().to_path_buf(), + codex_state::SqliteConfig::new_for_testing(home.path().abs()), "test-provider".to_string(), /*backfill_lease_seconds*/ 60, ) @@ -114,9 +164,11 @@ async fn reconcile_rollout_preserves_existing_explicit_title() -> anyhow::Result let thread_id = ThreadId::new(); let rollout_path = write_rollout_with_user_message(home.path(), thread_id, "Hey", ThreadHistoryMode::Legacy)?; - let runtime = - codex_state::StateRuntime::init(home.path().to_path_buf(), "test-provider".to_string()) - .await?; + let runtime = codex_state::StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(home.path().abs()), + "test-provider".to_string(), + ) + .await?; let mut metadata = metadata::extract_metadata_from_rollout(rollout_path.as_path(), "test-provider") @@ -157,9 +209,11 @@ async fn reconcile_rollout_preserves_existing_paginated_memory_mode() -> anyhow: "Hey", ThreadHistoryMode::Paginated, )?; - let runtime = - codex_state::StateRuntime::init(home.path().to_path_buf(), "test-provider".to_string()) - .await?; + let runtime = codex_state::StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(home.path().abs()), + "test-provider".to_string(), + ) + .await?; reconcile_rollout( Some(runtime.as_ref()), diff --git a/codex-rs/rollout/src/tests.rs b/codex-rs/rollout/src/tests.rs index e504b716428c..e962da0eccd3 100644 --- a/codex-rs/rollout/src/tests.rs +++ b/codex-rs/rollout/src/tests.rs @@ -1,6 +1,7 @@ #![allow(warnings, clippy::all)] #![allow(clippy::unwrap_used, clippy::expect_used)] +use codex_utils_absolute_path::test_support::PathExt; use std::ffi::OsStr; use std::fs; use std::fs::File; @@ -62,9 +63,12 @@ async fn insert_state_db_thread( rollout_path: &Path, archived: bool, ) -> crate::state_db::StateDbHandle { - let runtime = codex_state::StateRuntime::init(home.to_path_buf(), TEST_PROVIDER.to_string()) - .await - .expect("state db should initialize"); + let runtime = codex_state::StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(home.abs()), + TEST_PROVIDER.to_string(), + ) + .await + .expect("state db should initialize"); runtime .mark_backfill_complete(/*last_watermark*/ None) .await @@ -227,9 +231,12 @@ async fn find_thread_path_repairs_missing_db_row_after_filesystem_fallback() { let fs_rollout_path = home.join(format!("sessions/2025/01/03/rollout-{ts}-{uuid}.jsonl")); // Create an empty state DB so lookup takes the DB-first path and then falls back to files. - let runtime = codex_state::StateRuntime::init(home.to_path_buf(), TEST_PROVIDER.to_string()) - .await - .expect("state db should initialize"); + let runtime = codex_state::StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(home.abs()), + TEST_PROVIDER.to_string(), + ) + .await + .expect("state db should initialize"); runtime .mark_backfill_complete(/*last_watermark*/ None) .await @@ -280,9 +287,12 @@ async fn assert_state_db_rollout_path( thread_id: ThreadId, expected_path: Option<&Path>, ) { - let runtime = codex_state::StateRuntime::init(home.to_path_buf(), TEST_PROVIDER.to_string()) - .await - .expect("state db should initialize"); + let runtime = codex_state::StateRuntime::init( + codex_state::SqliteConfig::new_for_testing(home.abs()), + TEST_PROVIDER.to_string(), + ) + .await + .expect("state db should initialize"); let path = runtime .find_rollout_path_by_id(thread_id, Some(false)) .await diff --git a/codex-rs/state/src/audit.rs b/codex-rs/state/src/audit.rs index eedde2b7b260..6772b916e7ea 100644 --- a/codex-rs/state/src/audit.rs +++ b/codex-rs/state/src/audit.rs @@ -1,11 +1,11 @@ //! Read-only state database queries for diagnostics. use anyhow::Result; -use codex_utils_absolute_path::AbsolutePathBuf; use sqlx::Row; -use std::path::Path; use std::path::PathBuf; +use crate::SqliteConfig; + /// Minimal thread metadata used by read-only state database audits. #[derive(Debug, Clone, PartialEq, Eq)] pub struct ThreadStateAuditRow { @@ -17,11 +17,10 @@ pub struct ThreadStateAuditRow { } /// Read persisted thread rows from a state DB without creating, migrating, or repairing it. -pub async fn read_thread_state_audit_rows(path: &Path) -> Result> { - let sqlite = crate::SqliteConfig::from_sqlite_home(AbsolutePathBuf::try_from( - path.parent().unwrap_or(path), - )?); - let pool = sqlite.open_read_only_pool(path).await?; +pub async fn read_thread_state_audit_rows( + sqlite: &SqliteConfig, +) -> Result> { + let pool = sqlite.open_read_only_pool(&sqlite.state_db_path()).await?; let rows = sqlx::query( r#" SELECT id, rollout_path, archived, source, model_provider diff --git a/codex-rs/state/src/bin/logs_client.rs b/codex-rs/state/src/bin/logs_client.rs index 8002226da1a6..9a3c8fa099b1 100644 --- a/codex-rs/state/src/bin/logs_client.rs +++ b/codex-rs/state/src/bin/logs_client.rs @@ -113,7 +113,11 @@ async fn main() -> anyhow::Result<()> { .parent() .map(ToOwned::to_owned) .unwrap_or_else(|| PathBuf::from(".")); - let runtime = StateRuntime::init(codex_home, "logs-client".to_string()).await?; + let runtime = StateRuntime::init( + SqliteConfig::from_sqlite_home(AbsolutePathBuf::relative_to_current_dir(codex_home)?), + "logs-client".to_string(), + ) + .await?; let mut last_id = print_backfill(runtime.as_ref(), &filter, args.backfill, args.compact).await?; diff --git a/codex-rs/state/src/log_db.rs b/codex-rs/state/src/log_db.rs index b62e8a43e07e..d02feb24f149 100644 --- a/codex-rs/state/src/log_db.rs +++ b/codex-rs/state/src/log_db.rs @@ -493,6 +493,7 @@ mod tests { use std::sync::Arc; use std::sync::Mutex; + use codex_utils_absolute_path::test_support::PathExt; use pretty_assertions::assert_eq; use tracing_subscriber::filter::Targets; use tracing_subscriber::fmt::writer::MakeWriter; @@ -583,9 +584,12 @@ mod tests { #[tokio::test] async fn sqlite_feedback_logs_match_feedback_formatter_shape() { let codex_home = temp_codex_home(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let writer = SharedWriter::default(); let layer = start(runtime.clone()); @@ -641,9 +645,12 @@ mod tests { #[tokio::test] async fn flush_persists_logs_for_query() { let codex_home = temp_codex_home(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let layer = start(runtime.clone()); let guard = tracing_subscriber::registry() @@ -672,9 +679,12 @@ mod tests { #[tokio::test] async fn configured_batch_size_flushes_without_explicit_flush() { let codex_home = temp_codex_home(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let layer = LogDbLayer::start_with_config( runtime.clone(), LogSinkQueueConfig { @@ -721,9 +731,12 @@ mod tests { #[tokio::test] async fn configured_flush_interval_persists_buffered_logs() { let codex_home = temp_codex_home(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let layer = LogDbLayer::start_with_config( runtime.clone(), LogSinkQueueConfig { diff --git a/codex-rs/state/src/log_db_filter_tests.rs b/codex-rs/state/src/log_db_filter_tests.rs index 6c6380d33472..cd44993d2cf4 100644 --- a/codex-rs/state/src/log_db_filter_tests.rs +++ b/codex-rs/state/src/log_db_filter_tests.rs @@ -1,3 +1,4 @@ +use codex_utils_absolute_path::test_support::PathExt; use pretty_assertions::assert_eq; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; @@ -9,9 +10,12 @@ use super::*; async fn sqlite_sink_drops_low_level_opentelemetry_sdk_logs() { let codex_home = std::env::temp_dir().join(format!("codex-state-log-db-filter-{}", Uuid::new_v4())); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let layer = start(runtime.clone()); let guard = tracing_subscriber::registry() diff --git a/codex-rs/state/src/runtime.rs b/codex-rs/state/src/runtime.rs index c247b93be143..79f8e96f2153 100644 --- a/codex-rs/state/src/runtime.rs +++ b/codex-rs/state/src/runtime.rs @@ -24,7 +24,6 @@ use chrono::DateTime; use chrono::Utc; use codex_protocol::ThreadId; use codex_protocol::protocol::RolloutItem; -use codex_utils_absolute_path::AbsolutePathBuf; use serde_json::Value; use sqlx::QueryBuilder; use sqlx::Row; @@ -80,7 +79,7 @@ const LOG_PARTITION_ROW_LIMIT: i64 = 1_000; #[derive(Clone)] pub struct StateRuntime { - codex_home: PathBuf, + sqlite: SqliteConfig, default_provider: String, pool: Arc, logs_pool: Arc, @@ -91,36 +90,31 @@ pub struct StateRuntime { } impl StateRuntime { - /// Initialize the state runtime using the provided Codex home and default provider. + /// Initialize the state runtime using the provided SQLite configuration and default provider. /// - /// This opens (and migrates) the SQLite databases under `codex_home`. + /// This opens (and migrates) the SQLite databases under the configured + /// `sqlite_home`. /// Logs and paginated thread history live in dedicated files to reduce /// lock contention with the rest of the state store. - pub async fn init(codex_home: PathBuf, default_provider: String) -> anyhow::Result> { - Self::init_inner( - codex_home, - default_provider, - /*telemetry_override*/ None, - ) - .await + pub async fn init(sqlite: SqliteConfig, default_provider: String) -> anyhow::Result> { + Self::init_inner(sqlite, default_provider, /*telemetry_override*/ None).await } #[cfg(test)] pub(crate) async fn init_with_telemetry_for_tests( - codex_home: PathBuf, + sqlite: SqliteConfig, default_provider: String, telemetry_override: &dyn DbTelemetry, ) -> anyhow::Result> { - Self::init_inner(codex_home, default_provider, Some(telemetry_override)).await + Self::init_inner(sqlite, default_provider, Some(telemetry_override)).await } async fn init_inner( - codex_home: PathBuf, + sqlite: SqliteConfig, default_provider: String, telemetry_override: Option<&dyn DbTelemetry>, ) -> anyhow::Result> { - let sqlite = SqliteConfig::from_sqlite_home(AbsolutePathBuf::try_from(codex_home.clone())?); - tokio::fs::create_dir_all(&codex_home).await?; + tokio::fs::create_dir_all(sqlite.home()).await?; let state_migrator = runtime_state_migrator(); let logs_migrator = runtime_logs_migrator(); let goals_migrator = runtime_goals_migrator(); @@ -230,7 +224,7 @@ impl StateRuntime { memories: MemoryStore::new(Arc::clone(&memories_pool), Arc::clone(&pool)), pool, logs_pool, - codex_home, + sqlite, default_provider, thread_updated_at_millis: Arc::new(AtomicI64::new(thread_updated_at_millis)), thread_recency_at_millis: Arc::new(AtomicI64::new(thread_recency_at_millis)), @@ -244,9 +238,9 @@ impl StateRuntime { Ok(runtime) } - /// Return the configured Codex home directory for this runtime. - pub fn codex_home(&self) -> &Path { - self.codex_home.as_path() + /// Return the SQLite configuration for this runtime. + pub fn sqlite(&self) -> &SqliteConfig { + &self.sqlite } pub fn thread_goals(&self) -> &GoalStore { @@ -265,8 +259,7 @@ impl StateRuntime { self.pool.close().await; } - pub async fn clear_memory_data_in_sqlite_home(sqlite_home: &Path) -> anyhow::Result { - let sqlite = SqliteConfig::from_sqlite_home(AbsolutePathBuf::try_from(sqlite_home)?); + pub async fn clear_memory_data_in_sqlite_home(sqlite: &SqliteConfig) -> anyhow::Result { let memories_path = sqlite.memories_db_path(); if !tokio::fs::try_exists(&memories_path).await? { return Ok(false); @@ -289,8 +282,7 @@ async fn close_sqlite_pools(pools: &[&SqlitePool]) { } /// Open and migrate the rebuildable paginated thread-history database. -pub async fn open_thread_history_db(sqlite_home: &Path) -> anyhow::Result { - let sqlite = SqliteConfig::from_sqlite_home(AbsolutePathBuf::try_from(sqlite_home)?); +pub async fn open_thread_history_db(sqlite: &SqliteConfig) -> anyhow::Result { let migrator = runtime_thread_history_migrator(); sqlite .open_thread_history_db(&migrator, /*telemetry_override*/ None) @@ -326,9 +318,10 @@ ON CONFLICT(id) DO NOTHING } /// Run SQLite's built-in integrity check against an existing database file. -pub async fn sqlite_integrity_check(path: &Path) -> anyhow::Result> { - let sqlite = - SqliteConfig::from_sqlite_home(AbsolutePathBuf::try_from(path.parent().unwrap_or(path))?); +pub async fn sqlite_integrity_check( + sqlite: &SqliteConfig, + path: &Path, +) -> anyhow::Result> { let pool = sqlite.open_read_only_pool(path).await?; let rows = sqlx::query_scalar::<_, String>("PRAGMA integrity_check") .fetch_all(&pool) @@ -431,7 +424,7 @@ mod tests { .expect("create sample table"); pool.close().await; - let result = sqlite_integrity_check(&path) + let result = sqlite_integrity_check(&sqlite, &path) .await .expect("integrity check should run"); @@ -477,7 +470,7 @@ mod tests { strict_pool.close().await; let tolerant_migrator = runtime_state_migrator(); - let tolerant_pool = crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()) + let tolerant_pool = sqlite .open_state_db(&tolerant_migrator, /*telemetry_override*/ None) .await .expect("runtime migrator should tolerate newer applied migrations"); @@ -492,7 +485,7 @@ mod tests { let telemetry = TestTelemetry::default(); let runtime = StateRuntime::init_with_telemetry_for_tests( - codex_home.clone(), + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), "test-provider".to_string(), &telemetry, ) diff --git a/codex-rs/state/src/runtime/backfill.rs b/codex-rs/state/src/runtime/backfill.rs index 1a0074e52b8a..1acbf0127434 100644 --- a/codex-rs/state/src/runtime/backfill.rs +++ b/codex-rs/state/src/runtime/backfill.rs @@ -119,9 +119,12 @@ mod tests { #[tokio::test] async fn backfill_state_persists_progress_and_completion() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let initial = runtime .get_backfill_state() @@ -172,10 +175,10 @@ mod tests { #[tokio::test] async fn get_backfill_state_succeeds_while_another_connection_holds_writer_slot() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) + let sqlite = crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()); + let runtime = StateRuntime::init(sqlite.clone(), "test-provider".to_string()) .await .expect("initialize runtime"); - let sqlite = crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()); let write_pool = sqlite .open_read_write_pool(&sqlite.state_db_path()) .await @@ -202,9 +205,12 @@ mod tests { #[tokio::test] async fn get_backfill_state_repairs_a_missing_singleton_row() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); sqlx::query("DELETE FROM backfill_state WHERE id = 1") .execute(runtime.pool.as_ref()) .await @@ -228,9 +234,12 @@ mod tests { #[tokio::test] async fn backfill_claim_is_singleton_until_stale_and_blocked_when_complete() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let claimed = runtime .try_claim_backfill(/*lease_seconds*/ 3600) diff --git a/codex-rs/state/src/runtime/external_agent_config_imports_tests.rs b/codex-rs/state/src/runtime/external_agent_config_imports_tests.rs index 9680744599a5..a3040ba6515c 100644 --- a/codex-rs/state/src/runtime/external_agent_config_imports_tests.rs +++ b/codex-rs/state/src/runtime/external_agent_config_imports_tests.rs @@ -1,10 +1,15 @@ use super::*; use crate::runtime::test_support::unique_temp_dir; +use codex_utils_absolute_path::test_support::PathExt; use pretty_assertions::assert_eq; #[tokio::test] async fn records_completion_by_import_id() -> anyhow::Result<()> { - let runtime = StateRuntime::init(unique_temp_dir(), "test-provider".to_string()).await?; + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(unique_temp_dir().as_path().abs()), + "test-provider".to_string(), + ) + .await?; runtime .record_external_agent_config_import_completed( @@ -127,7 +132,11 @@ async fn records_completion_by_import_id() -> anyhow::Result<()> { #[tokio::test] async fn reads_all_history_records() -> anyhow::Result<()> { - let runtime = StateRuntime::init(unique_temp_dir(), "test-provider".to_string()).await?; + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(unique_temp_dir().as_path().abs()), + "test-provider".to_string(), + ) + .await?; runtime .record_external_agent_config_import_completed( diff --git a/codex-rs/state/src/runtime/goals.rs b/codex-rs/state/src/runtime/goals.rs index 197327a76be5..ed616e16b7c8 100644 --- a/codex-rs/state/src/runtime/goals.rs +++ b/codex-rs/state/src/runtime/goals.rs @@ -634,12 +634,16 @@ mod tests { use super::*; use crate::runtime::test_support::test_thread_metadata; use crate::runtime::test_support::unique_temp_dir; + use codex_utils_absolute_path::test_support::PathExt; use pretty_assertions::assert_eq; async fn test_runtime() -> std::sync::Arc { - StateRuntime::init(unique_temp_dir(), "test-provider".to_string()) - .await - .expect("state db should initialize") + StateRuntime::init( + crate::SqliteConfig::new_for_testing(unique_temp_dir().as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("state db should initialize") } fn test_thread_id() -> ThreadId { @@ -647,11 +651,8 @@ mod tests { } async fn upsert_test_thread(runtime: &StateRuntime, thread_id: ThreadId) { - let metadata = test_thread_metadata( - runtime.codex_home(), - thread_id, - runtime.codex_home().join("workspace"), - ); + let sqlite_home = runtime.sqlite().home(); + let metadata = test_thread_metadata(sqlite_home, thread_id, sqlite_home.join("workspace")); runtime .upsert_thread(&metadata) .await diff --git a/codex-rs/state/src/runtime/logs.rs b/codex-rs/state/src/runtime/logs.rs index 29a4efe7a0a2..d905d9cfdb9b 100644 --- a/codex-rs/state/src/runtime/logs.rs +++ b/codex-rs/state/src/runtime/logs.rs @@ -574,9 +574,12 @@ mod tests { #[tokio::test] async fn insert_logs_use_dedicated_log_database() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); runtime .insert_logs(&[LogEntry { @@ -648,9 +651,12 @@ mod tests { pool.close().await; drop(pool); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let rows = runtime .query_logs(&LogQuery::default()) @@ -704,9 +710,12 @@ mod tests { #[tokio::test] async fn init_configures_logs_db_with_incremental_auto_vacuum() { let codex_home = unique_temp_dir(); - let _runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let _runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let logs_path = crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()).logs_db_path(); @@ -750,9 +759,12 @@ mod tests { #[tokio::test] async fn query_logs_with_search_matches_rendered_body_substring() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); runtime .insert_logs(&[ @@ -803,9 +815,12 @@ mod tests { #[tokio::test] async fn query_logs_filters_level_set_without_rewriting_stored_level() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); runtime .insert_logs(&[ @@ -888,9 +903,12 @@ mod tests { #[tokio::test] async fn insert_logs_prunes_old_rows_when_thread_exceeds_size_limit() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let six_mebibytes = "a".repeat(6 * 1024 * 1024); runtime @@ -942,9 +960,12 @@ mod tests { #[tokio::test] async fn insert_logs_prunes_single_thread_row_when_it_exceeds_size_limit() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let eleven_mebibytes = "d".repeat(11 * 1024 * 1024); runtime @@ -980,9 +1001,12 @@ mod tests { #[tokio::test] async fn insert_logs_prunes_threadless_rows_per_process_uuid_only() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let six_mebibytes = "b".repeat(6 * 1024 * 1024); runtime @@ -1049,9 +1073,12 @@ mod tests { #[tokio::test] async fn insert_logs_prunes_single_threadless_process_row_when_it_exceeds_size_limit() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let eleven_mebibytes = "e".repeat(11 * 1024 * 1024); runtime @@ -1087,9 +1114,12 @@ mod tests { #[tokio::test] async fn insert_logs_prunes_threadless_rows_with_null_process_uuid() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let six_mebibytes = "c".repeat(6 * 1024 * 1024); runtime @@ -1155,9 +1185,12 @@ mod tests { #[tokio::test] async fn insert_logs_prunes_single_threadless_null_process_row_when_it_exceeds_limit() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let eleven_mebibytes = "f".repeat(11 * 1024 * 1024); runtime @@ -1193,9 +1226,12 @@ mod tests { #[tokio::test] async fn insert_logs_prunes_old_rows_when_thread_exceeds_row_limit() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let entries: Vec = (1..=1_001) .map(|ts| LogEntry { @@ -1236,9 +1272,12 @@ mod tests { #[tokio::test] async fn insert_logs_prunes_old_threadless_rows_when_process_exceeds_row_limit() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let entries: Vec = (1..=1_001) .map(|ts| LogEntry { @@ -1283,9 +1322,12 @@ mod tests { #[tokio::test] async fn insert_logs_prunes_old_threadless_null_process_rows_when_row_limit_exceeded() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let entries: Vec = (1..=1_001) .map(|ts| LogEntry { @@ -1330,9 +1372,12 @@ mod tests { #[tokio::test] async fn query_feedback_logs_returns_newest_lines_within_limit_in_order() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); runtime .insert_logs(&[ @@ -1400,9 +1445,12 @@ mod tests { #[tokio::test] async fn query_feedback_logs_excludes_oversized_newest_row() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let eleven_mebibytes = "z".repeat(11 * 1024 * 1024); runtime @@ -1450,9 +1498,12 @@ mod tests { #[tokio::test] async fn query_feedback_logs_includes_threadless_rows_from_same_process() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); runtime .insert_logs(&[ @@ -1548,9 +1599,12 @@ mod tests { #[tokio::test] async fn query_feedback_logs_excludes_threadless_rows_from_prior_processes() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); runtime .insert_logs(&[ @@ -1646,9 +1700,12 @@ mod tests { #[tokio::test] async fn query_feedback_logs_keeps_newest_suffix_across_thread_and_threadless_logs() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let thread_marker = "thread-scoped-oldest"; let threadless_older_marker = "threadless-older"; let threadless_newer_marker = "threadless-newer"; @@ -1721,9 +1778,12 @@ mod tests { #[tokio::test] async fn query_feedback_logs_for_threads_merges_requested_threads_and_threadless_rows() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); runtime .insert_logs(&[ @@ -1841,9 +1901,12 @@ mod tests { #[tokio::test] async fn query_feedback_logs_for_threads_returns_empty_for_empty_thread_list() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let bytes = runtime .query_feedback_logs_for_threads(&[]) diff --git a/codex-rs/state/src/runtime/memories.rs b/codex-rs/state/src/runtime/memories.rs index 39fb560ab186..c620dcd61752 100644 --- a/codex-rs/state/src/runtime/memories.rs +++ b/codex-rs/state/src/runtime/memories.rs @@ -1681,6 +1681,7 @@ mod tests { use chrono::Utc; use codex_protocol::ThreadId; use codex_protocol::protocol::ThreadHistoryMode; + use codex_utils_absolute_path::test_support::PathExt; use pretty_assertions::assert_eq; use sqlx::Row; use std::sync::Arc; @@ -1707,9 +1708,12 @@ mod tests { #[tokio::test] async fn stage1_claim_skips_when_up_to_date() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let thread_id = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id"); let metadata = test_thread_metadata(&codex_home, thread_id, codex_home.join("a")); @@ -1775,9 +1779,12 @@ mod tests { #[tokio::test] async fn stage1_running_stale_can_be_stolen_but_fresh_running_is_skipped() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let thread_id = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id"); let owner_a = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id"); @@ -1830,9 +1837,12 @@ mod tests { #[tokio::test] async fn stage1_concurrent_claim_for_same_thread_is_conflict_safe() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let thread_id = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id"); runtime @@ -1898,9 +1908,12 @@ mod tests { #[tokio::test] async fn stage1_concurrent_claims_respect_running_cap() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let thread_a = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id"); let thread_b = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id"); @@ -1966,9 +1979,12 @@ mod tests { #[tokio::test] async fn claim_stage1_jobs_filters_by_age_idle_and_current_thread() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let now = Utc::now(); let fresh_at = now - Duration::hours(1); @@ -2056,9 +2072,12 @@ mod tests { #[tokio::test] async fn claim_stage1_jobs_bounds_state_scan_before_memory_probes() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let now = Utc::now(); let eligible_newer_at = now - Duration::hours(13); @@ -2171,9 +2190,12 @@ mod tests { #[tokio::test] async fn claim_stage1_jobs_skips_threads_without_enabled_memory() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let now = Utc::now(); let eligible_at = now - Duration::hours(13); @@ -2267,9 +2289,12 @@ mod tests { #[tokio::test] async fn clear_memory_data_clears_rows_and_preserves_thread_memory_modes() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let now = Utc::now() - Duration::hours(13); let worker_id = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("worker id"); @@ -2376,9 +2401,12 @@ mod tests { #[tokio::test] async fn claim_stage1_jobs_enforces_global_running_cap() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let current_thread_id = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("current thread id"); @@ -2503,9 +2531,12 @@ WHERE kind = 'memory_stage1' #[tokio::test] async fn claim_stage1_jobs_processes_two_full_batches_across_startup_passes() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let current_thread_id = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("current thread id"); @@ -2590,9 +2621,12 @@ WHERE kind = 'memory_stage1' #[tokio::test] async fn delete_thread_removes_stage1_output_and_enqueues_phase2_when_selected() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let thread_id = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id"); let owner = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id"); @@ -2715,9 +2749,12 @@ WHERE kind = ? AND job_key = ? #[tokio::test] async fn mark_stage1_job_succeeded_no_output_skips_phase2_when_output_was_already_absent() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let thread_id = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id"); let owner = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id"); @@ -2790,9 +2827,12 @@ WHERE kind = ? AND job_key = ? #[tokio::test] async fn mark_stage1_job_succeeded_no_output_enqueues_phase2_when_deleting_output() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let thread_id = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id"); let owner = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id"); @@ -2915,9 +2955,12 @@ WHERE kind = ? AND job_key = ? #[tokio::test] async fn stage1_retry_exhaustion_does_not_block_newer_watermark() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let thread_id = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id"); let owner = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id"); @@ -3011,9 +3054,12 @@ WHERE kind = ? AND job_key = ? #[tokio::test] async fn phase2_global_lock_respects_success_cooldown() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let owner = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id"); @@ -3073,9 +3119,12 @@ WHERE kind = ? AND job_key = ? #[tokio::test] async fn phase2_global_lock_can_be_claimed_after_retry_budget_is_exhausted() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); runtime .enqueue_global_consolidation(/*input_watermark*/ 100) @@ -3143,9 +3192,12 @@ WHERE kind = ? AND job_key = ? #[tokio::test] async fn list_stage1_outputs_for_global_returns_latest_outputs() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let thread_id_a = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id"); let thread_id_b = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id"); @@ -3246,9 +3298,12 @@ WHERE kind = ? AND job_key = ? #[tokio::test] async fn list_stage1_outputs_for_global_skips_empty_payloads() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let thread_id_non_empty = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id"); @@ -3315,9 +3370,12 @@ VALUES (?, ?, ?, ?, ?) #[tokio::test] async fn list_stage1_outputs_for_global_includes_paginated_and_skips_polluted_threads() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let thread_id_enabled = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id"); @@ -3382,9 +3440,12 @@ VALUES (?, ?, ?, ?, ?) #[tokio::test] async fn get_phase2_input_selection_returns_current_selected_rows() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let thread_id_a = stable_thread_id("00000000-0000-4000-8000-000000000001"); let thread_id_b = stable_thread_id("00000000-0000-4000-8000-000000000002"); @@ -3497,9 +3558,12 @@ VALUES (?, ?, ?, ?, ?) #[tokio::test] async fn get_phase2_input_selection_excludes_polluted_previous_selection() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let thread_id_enabled = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id"); @@ -3590,9 +3654,12 @@ VALUES (?, ?, ?, ?, ?) #[tokio::test] async fn mark_thread_memory_mode_polluted_enqueues_phase2_for_selected_threads() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let thread_id = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id"); let owner = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id"); @@ -3679,9 +3746,12 @@ VALUES (?, ?, ?, ?, ?) #[tokio::test] async fn mark_thread_memory_mode_polluted_enqueues_phase2_when_already_polluted() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let thread_id = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id"); let owner = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id"); @@ -3774,9 +3844,12 @@ VALUES (?, ?, ?, ?, ?) #[tokio::test] async fn get_phase2_input_selection_returns_regenerated_selected_rows() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let thread_id = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id"); let owner = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id"); @@ -3893,9 +3966,12 @@ VALUES (?, ?, ?, ?, ?) #[tokio::test] async fn get_phase2_input_selection_uses_current_ranking_after_refreshes() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let thread_id_a = stable_thread_id("00000000-0000-4000-8000-000000000001"); let thread_id_b = stable_thread_id("00000000-0000-4000-8000-000000000002"); @@ -4036,9 +4112,12 @@ VALUES (?, ?, ?, ?, ?) #[tokio::test] async fn mark_global_phase2_job_succeeded_updates_selected_snapshot_timestamp() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let thread_id = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id"); let owner = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id"); @@ -4186,9 +4265,12 @@ VALUES (?, ?, ?, ?, ?) #[tokio::test] async fn mark_global_phase2_job_succeeded_only_marks_exact_selected_snapshots() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let thread_id = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id"); let owner = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id"); @@ -4306,9 +4388,12 @@ VALUES (?, ?, ?, ?, ?) #[tokio::test] async fn record_stage1_output_usage_updates_usage_metadata() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let thread_a = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id a"); let thread_b = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id b"); @@ -4425,9 +4510,12 @@ VALUES (?, ?, ?, ?, ?) #[tokio::test] async fn get_phase2_input_selection_prioritizes_usage_count_then_recent_usage() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let now = Utc::now(); let owner = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id"); @@ -4521,9 +4609,12 @@ VALUES (?, ?, ?, ?, ?) #[tokio::test] async fn get_phase2_input_selection_excludes_stale_used_memories_but_keeps_fresh_never_used() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let now = Utc::now(); let owner = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id"); @@ -4617,9 +4708,12 @@ VALUES (?, ?, ?, ?, ?) #[tokio::test] async fn get_phase2_input_selection_prefers_recent_thread_updates_over_recent_generation() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let owner = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id"); let older_thread = @@ -4703,9 +4797,12 @@ VALUES (?, ?, ?, ?, ?) #[tokio::test] async fn prune_stage1_outputs_for_retention_prunes_stale_unselected_rows_only() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let owner = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id"); let stale_unused = @@ -4845,9 +4942,12 @@ VALUES (?, ?, ?, ?, ?) #[tokio::test] async fn prune_stage1_outputs_for_retention_respects_batch_limit() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let owner = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner id"); let thread_a = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread a"); @@ -4923,9 +5023,12 @@ VALUES (?, ?, ?, ?, ?) #[tokio::test] async fn mark_stage1_job_succeeded_enqueues_global_consolidation() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let thread_a = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id a"); let thread_b = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("thread id b"); @@ -5018,9 +5121,12 @@ VALUES (?, ?, ?, ?, ?) #[tokio::test] async fn phase2_global_lock_allows_only_one_fresh_runner() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); runtime .enqueue_global_consolidation(/*input_watermark*/ 200) @@ -5051,9 +5157,12 @@ VALUES (?, ?, ?, ?, ?) #[tokio::test] async fn phase2_global_lock_creates_missing_job_row() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let owner_a = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner a"); let owner_b = ThreadId::from_string(&Uuid::new_v4().to_string()).expect("owner b"); @@ -5101,9 +5210,12 @@ VALUES (?, ?, ?, ?, ?) #[tokio::test] async fn phase2_global_lock_stale_lease_allows_takeover() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); runtime .enqueue_global_consolidation(/*input_watermark*/ 300) @@ -5176,9 +5288,12 @@ VALUES (?, ?, ?, ?, ?) #[tokio::test] async fn enqueue_global_consolidation_keeps_phase2_input_watermark_monotonic() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); runtime .enqueue_global_consolidation(/*input_watermark*/ 500) @@ -5240,9 +5355,12 @@ VALUES (?, ?, ?, ?, ?) #[tokio::test] async fn phase2_failure_fallback_updates_unowned_running_job() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); runtime .enqueue_global_consolidation(/*input_watermark*/ 400) diff --git a/codex-rs/state/src/runtime/recovery_tests.rs b/codex-rs/state/src/runtime/recovery_tests.rs index bb14710804f4..3861000dd381 100644 --- a/codex-rs/state/src/runtime/recovery_tests.rs +++ b/codex-rs/state/src/runtime/recovery_tests.rs @@ -86,7 +86,12 @@ async fn runtime_db_path_for_corruption_error_returns_failed_database_path() -> let path = crate::SqliteConfig::new_for_testing(sqlite_home.as_path().abs()).state_db_path(); tokio::fs::write(path.as_path(), b"not sqlite").await?; - let err = match super::super::StateRuntime::init(sqlite_home, "openai".to_string()).await { + let err = match super::super::StateRuntime::init( + crate::SqliteConfig::new_for_testing(sqlite_home.as_path().abs()), + "openai".to_string(), + ) + .await + { Ok(_) => panic!("malformed sqlite should fail to initialize"), Err(err) => err, }; diff --git a/codex-rs/state/src/runtime/remote_control.rs b/codex-rs/state/src/runtime/remote_control.rs index 4831fdef6592..f09bb015d3f3 100644 --- a/codex-rs/state/src/runtime/remote_control.rs +++ b/codex-rs/state/src/runtime/remote_control.rs @@ -165,9 +165,12 @@ mod tests { #[tokio::test] async fn remote_control_enrollment_round_trips_by_target_and_account() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); runtime .upsert_remote_control_enrollment(&RemoteControlEnrollmentRecord { @@ -245,9 +248,12 @@ mod tests { #[tokio::test] async fn delete_remote_control_enrollment_removes_only_matching_entry() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); runtime .upsert_remote_control_enrollment(&RemoteControlEnrollmentRecord { @@ -365,9 +371,12 @@ mod tests { .expect("insert legacy enrollment"); pool.close().await; - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("initialize runtime"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("initialize runtime"); let actual = runtime .get_remote_control_enrollment( "wss://example.com/backend-api/wham/remote/control/server", diff --git a/codex-rs/state/src/runtime/threads.rs b/codex-rs/state/src/runtime/threads.rs index fbf0655ffbd2..584a5b9910a2 100644 --- a/codex-rs/state/src/runtime/threads.rs +++ b/codex-rs/state/src/runtime/threads.rs @@ -1418,15 +1418,19 @@ mod tests { use codex_protocol::protocol::SessionMetaLine; use codex_protocol::protocol::SessionSource; use codex_protocol::protocol::ThreadHistoryMode; + use codex_utils_absolute_path::test_support::PathExt; use pretty_assertions::assert_eq; use std::path::PathBuf; #[tokio::test] async fn upsert_thread_keeps_creation_memory_mode_for_existing_rows() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("state db should initialize"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("state db should initialize"); let thread_id = ThreadId::from_string("00000000-0000-0000-0000-000000000123").expect("valid thread id"); let mut metadata = test_thread_metadata(&codex_home, thread_id, codex_home.clone()); @@ -1462,9 +1466,12 @@ mod tests { #[tokio::test] async fn thread_metadata_round_trips_history_mode() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("state db should initialize"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("state db should initialize"); let thread_id = ThreadId::from_string("00000000-0000-0000-0000-000000000124").expect("valid thread id"); let mut metadata = test_thread_metadata(&codex_home, thread_id, codex_home.clone()); @@ -1486,9 +1493,12 @@ mod tests { #[tokio::test] async fn thread_pin_updates_round_trip_and_survive_rollout_reconciliation() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("state db should initialize"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("state db should initialize"); let thread_id = ThreadId::new(); let metadata = test_thread_metadata(&codex_home, thread_id, codex_home.clone()); runtime @@ -1557,9 +1567,12 @@ mod tests { #[tokio::test] async fn list_threads_filters_pins_before_recency_pagination_and_uses_index() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("state db should initialize"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("state db should initialize"); let oldest_pinned = ThreadId::from_string("00000000-0000-0000-0000-000000000041").unwrap(); let newest_unpinned = ThreadId::from_string("00000000-0000-0000-0000-000000000042").unwrap(); @@ -1653,7 +1666,11 @@ mod tests { #[tokio::test] async fn delete_thread_cleans_associated_state() -> Result<()> { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()).await?; + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await?; let thread_id = ThreadId::from_string("00000000-0000-0000-0000-000000000401")?; let child_thread_id = ThreadId::from_string("00000000-0000-0000-0000-000000000402")?; runtime @@ -1699,7 +1716,11 @@ mod tests { #[tokio::test] async fn delete_thread_keeps_retry_graph_on_cleanup_failure() -> Result<()> { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()).await?; + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await?; let thread_id = ThreadId::from_string("00000000-0000-0000-0000-000000000405")?; let child_thread_id = ThreadId::from_string("00000000-0000-0000-0000-000000000406")?; runtime @@ -1782,9 +1803,12 @@ mod tests { #[tokio::test] async fn list_threads_updated_after_returns_oldest_changes_first() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("state db should initialize"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("state db should initialize"); let older_id = ThreadId::from_string("00000000-0000-0000-0000-000000000001").expect("valid thread id"); let middle_id = @@ -1870,9 +1894,12 @@ mod tests { #[tokio::test] async fn list_threads_filters_by_cwd() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("state db should initialize"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("state db should initialize"); let first_id = ThreadId::from_string("00000000-0000-0000-0000-000000000101").expect("valid thread id"); let second_id = @@ -1981,9 +2008,12 @@ mod tests { #[tokio::test] async fn list_threads_uses_indexes_matching_cwd_filters() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home, "test-provider".to_string()) - .await - .expect("state db should initialize"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("state db should initialize"); let model_providers = ["test-provider".to_string()]; let cwd_filters = [ @@ -2068,9 +2098,12 @@ mod tests { #[tokio::test] async fn list_threads_by_relation_filters_spawn_graph_with_keyset_pagination() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("state db should initialize"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("state db should initialize"); let parent_id = ThreadId::new(); let first_child_id = ThreadId::from_string("00000000-0000-0000-0000-000000000001").expect("valid thread id"); @@ -2264,9 +2297,12 @@ mod tests { #[tokio::test] async fn apply_rollout_items_restores_memory_mode_from_session_meta() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("state db should initialize"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("state db should initialize"); let thread_id = ThreadId::from_string("00000000-0000-0000-0000-000000000456").expect("valid thread id"); let metadata = test_thread_metadata(&codex_home, thread_id, codex_home.clone()); @@ -2329,9 +2365,12 @@ mod tests { #[tokio::test] async fn apply_rollout_items_preserves_existing_git_branch_and_fills_missing_git_fields() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("state db should initialize"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("state db should initialize"); let thread_id = ThreadId::from_string("00000000-0000-0000-0000-000000000457").expect("valid thread id"); let mut metadata = test_thread_metadata(&codex_home, thread_id, codex_home.clone()); @@ -2406,9 +2445,12 @@ mod tests { #[tokio::test] async fn upsert_thread_preserves_existing_git_fields_atomically() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("state db should initialize"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("state db should initialize"); let thread_id = ThreadId::from_string("00000000-0000-0000-0000-000000000458").expect("valid thread id"); let mut metadata = test_thread_metadata(&codex_home, thread_id, codex_home.clone()); @@ -2447,9 +2489,12 @@ mod tests { #[tokio::test] async fn upsert_thread_preserves_existing_preview_when_incoming_preview_is_empty() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("state db should initialize"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("state db should initialize"); let thread_id = ThreadId::from_string("00000000-0000-0000-0000-000000000459").expect("valid thread id"); let mut metadata = test_thread_metadata(&codex_home, thread_id, codex_home.clone()); @@ -2480,9 +2525,12 @@ mod tests { #[tokio::test] async fn set_thread_preview_if_empty_only_fills_blank_preview() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("state db should initialize"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("state db should initialize"); let thread_id = ThreadId::from_string("00000000-0000-0000-0000-000000000460").expect("valid thread id"); let mut metadata = test_thread_metadata(&codex_home, thread_id, codex_home.clone()); @@ -2521,9 +2569,12 @@ mod tests { #[tokio::test] async fn update_thread_git_info_preserves_newer_non_git_metadata() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("state db should initialize"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("state db should initialize"); let thread_id = ThreadId::from_string("00000000-0000-0000-0000-000000000789").expect("valid thread id"); let metadata = test_thread_metadata(&codex_home, thread_id, codex_home.clone()); @@ -2583,9 +2634,12 @@ mod tests { #[tokio::test] async fn insert_thread_if_absent_preserves_existing_metadata() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("state db should initialize"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("state db should initialize"); let thread_id = ThreadId::from_string("00000000-0000-0000-0000-000000000791").expect("valid thread id"); @@ -2631,9 +2685,12 @@ mod tests { #[tokio::test] async fn update_thread_git_info_can_clear_fields() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("state db should initialize"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("state db should initialize"); let thread_id = ThreadId::from_string("00000000-0000-0000-0000-000000000790").expect("valid thread id"); let mut metadata = test_thread_metadata(&codex_home, thread_id, codex_home.clone()); @@ -2665,9 +2722,12 @@ mod tests { #[tokio::test] async fn touch_thread_updated_at_updates_only_updated_at() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("state db should initialize"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("state db should initialize"); let thread_id = ThreadId::from_string("00000000-0000-0000-0000-000000000791").expect("valid thread id"); let mut metadata = test_thread_metadata(&codex_home, thread_id, codex_home.clone()); @@ -2704,9 +2764,12 @@ mod tests { #[tokio::test] async fn touch_thread_recency_at_is_monotonic_and_survives_stale_upsert() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("state db should initialize"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("state db should initialize"); let thread_id = ThreadId::from_string("00000000-0000-0000-0000-000000000792").expect("valid thread id"); let mut metadata = test_thread_metadata(&codex_home, thread_id, codex_home.clone()); @@ -2763,9 +2826,12 @@ mod tests { #[tokio::test] async fn list_threads_orders_and_pages_by_recency_at() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("state db should initialize"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("state db should initialize"); let first_id = ThreadId::from_string("00000000-0000-0000-0000-000000000793").expect("valid thread id"); let second_id = @@ -2887,9 +2953,12 @@ mod tests { #[tokio::test] async fn thread_updated_at_uses_unique_epoch_millis_and_reads_legacy_seconds() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("state db should initialize"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("state db should initialize"); let first_id = ThreadId::from_string("00000000-0000-0000-0000-000000000901").expect("valid thread id"); let second_id = @@ -2995,9 +3064,12 @@ mod tests { #[tokio::test] async fn apply_rollout_items_uses_override_updated_at_when_provided() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home.clone(), "test-provider".to_string()) - .await - .expect("state db should initialize"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("state db should initialize"); let thread_id = ThreadId::from_string("00000000-0000-0000-0000-000000000792").expect("valid thread id"); let metadata = test_thread_metadata(&codex_home, thread_id, codex_home.clone()); @@ -3055,9 +3127,12 @@ mod tests { #[tokio::test] async fn thread_spawn_edges_track_directional_status() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home, "test-provider".to_string()) - .await - .expect("state db should initialize"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("state db should initialize"); let parent_thread_id = ThreadId::from_string("00000000-0000-0000-0000-000000000900").expect("valid thread id"); let child_thread_id = @@ -3151,9 +3226,12 @@ mod tests { #[tokio::test] async fn thread_spawn_children_without_status_filter_lists_all_statuses() { let codex_home = unique_temp_dir(); - let runtime = StateRuntime::init(codex_home, "test-provider".to_string()) - .await - .expect("state db should initialize"); + let runtime = StateRuntime::init( + crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()), + "test-provider".to_string(), + ) + .await + .expect("state db should initialize"); let parent_thread_id = ThreadId::from_string("00000000-0000-0000-0000-000000000910").expect("valid thread id"); let open_child_thread_id = diff --git a/codex-rs/thread-manager-sample/src/main.rs b/codex-rs/thread-manager-sample/src/main.rs index 41210239eeb2..81db5ca57fd3 100644 --- a/codex-rs/thread-manager-sample/src/main.rs +++ b/codex-rs/thread-manager-sample/src/main.rs @@ -45,6 +45,7 @@ use codex_core_api::RealtimeAudioConfig; use codex_core_api::RealtimeConfig; use codex_core_api::SessionPickerViewMode; use codex_core_api::SessionSource; +use codex_core_api::SqliteConfig; use codex_core_api::StartThreadOptions; use codex_core_api::TerminalResizeReflowConfig; use codex_core_api::ThreadManager; @@ -251,7 +252,7 @@ fn new_config(model: Option, arg0_paths: Arg0DispatchPaths) -> anyhow::R agent_max_depth: 1, agent_roles: BTreeMap::new(), memories: MemoriesConfig::default(), - sqlite_home: codex_home.to_path_buf(), + sqlite: SqliteConfig::from_sqlite_home(codex_home.clone()), log_dir: codex_home.join("log").to_path_buf(), config_lock_export_dir: None, config_lock_allow_codex_version_mismatch: false, diff --git a/codex-rs/thread-store/Cargo.toml b/codex-rs/thread-store/Cargo.toml index 42a8ece46334..fcbb00267316 100644 --- a/codex-rs/thread-store/Cargo.toml +++ b/codex-rs/thread-store/Cargo.toml @@ -33,6 +33,7 @@ tokio = { workspace = true } tracing = { workspace = true } [dev-dependencies] +codex-utils-absolute-path = { workspace = true } pretty_assertions = { workspace = true } tempfile = { workspace = true } tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } diff --git a/codex-rs/thread-store/src/local/archive_thread.rs b/codex-rs/thread-store/src/local/archive_thread.rs index 35ed6100cf8c..0d77f982192f 100644 --- a/codex-rs/thread-store/src/local/archive_thread.rs +++ b/codex-rs/thread-store/src/local/archive_thread.rs @@ -1,12 +1,11 @@ -use chrono::Utc; -use codex_rollout::find_thread_path_by_id_str; - use super::LocalThreadStore; use super::helpers::matching_rollout_file_name; use super::helpers::scoped_rollout_path; use crate::ArchiveThreadParams; use crate::ThreadStoreError; use crate::ThreadStoreResult; +use chrono::Utc; +use codex_rollout::find_thread_path_by_id_str; pub(super) async fn archive_thread( store: &LocalThreadStore, @@ -66,6 +65,7 @@ mod tests { use codex_protocol::ThreadId; use codex_protocol::protocol::SessionSource; use codex_rollout::ARCHIVED_SESSIONS_SUBDIR; + use codex_utils_absolute_path::test_support::PathExt; use pretty_assertions::assert_eq; use tempfile::TempDir; use uuid::Uuid; @@ -134,7 +134,7 @@ mod tests { let active_path = write_session_file(home.path(), "2025-01-03T12-00-00", uuid).expect("session file"); let runtime = codex_state::StateRuntime::init( - home.path().to_path_buf(), + codex_state::SqliteConfig::new_for_testing(home.path().abs()), config.default_model_provider_id.clone(), ) .await diff --git a/codex-rs/thread-store/src/local/create_thread.rs b/codex-rs/thread-store/src/local/create_thread.rs index 457f715dec7f..a9605c62e294 100644 --- a/codex-rs/thread-store/src/local/create_thread.rs +++ b/codex-rs/thread-store/src/local/create_thread.rs @@ -20,7 +20,7 @@ pub(super) async fn create_thread( })?; let config = RolloutConfig { codex_home: store.config.codex_home.clone(), - sqlite_home: store.config.sqlite.home().to_path_buf(), + sqlite: store.config.sqlite.clone(), cwd, model_provider_id: params.metadata.model_provider.clone(), generate_memories: matches!(params.metadata.memory_mode, ThreadMemoryMode::Enabled), diff --git a/codex-rs/thread-store/src/local/delete_thread.rs b/codex-rs/thread-store/src/local/delete_thread.rs index df75df56c5d9..b30c164844b2 100644 --- a/codex-rs/thread-store/src/local/delete_thread.rs +++ b/codex-rs/thread-store/src/local/delete_thread.rs @@ -223,6 +223,7 @@ mod tests { use codex_protocol::protocol::HistoryPosition; use codex_protocol::protocol::ThreadHistoryMode; use codex_protocol::protocol::ThreadMemoryMode; + use codex_utils_absolute_path::test_support::PathExt; use pretty_assertions::assert_eq; use tempfile::TempDir; use uuid::Uuid; @@ -420,9 +421,11 @@ mod tests { ThreadHistoryMode::Paginated, ) .expect("session file"); - let pool = codex_state::open_thread_history_db(home.path()) - .await - .expect("open thread history db"); + let pool = codex_state::open_thread_history_db( + &codex_state::SqliteConfig::new_for_testing(home.path().abs()), + ) + .await + .expect("open thread history db"); let thread_id_string = thread_id.to_string(); sqlx::query( "INSERT INTO thread_turns (thread_id, turn_id, rollout_ordinal, status) VALUES (?, 'turn-1', 1, 'completed')", diff --git a/codex-rs/thread-store/src/local/list_threads.rs b/codex-rs/thread-store/src/local/list_threads.rs index 3d416039b786..0e0044a40098 100644 --- a/codex-rs/thread-store/src/local/list_threads.rs +++ b/codex-rs/thread-store/src/local/list_threads.rs @@ -41,7 +41,7 @@ pub(super) async fn list_threads( let state_db = store.state_db().await; let rollout_config = RolloutConfig { codex_home: store.config.codex_home.clone(), - sqlite_home: store.config.sqlite.home().to_path_buf(), + sqlite: store.config.sqlite.clone(), cwd: store.config.codex_home.clone(), model_provider_id: store.config.default_model_provider_id.clone(), generate_memories: false, @@ -110,7 +110,7 @@ pub(super) async fn list_rollout_threads( }); let page = codex_rollout::state_db::list_threads_db( state_db.as_deref(), - config.codex_home.as_path(), + &config.sqlite, params.page_size, cursor, sort_key, @@ -202,6 +202,7 @@ mod tests { use codex_protocol::ThreadId; use codex_protocol::protocol::SessionSource; use codex_protocol::protocol::ThreadHistoryMode; + use codex_utils_absolute_path::test_support::PathExt; use pretty_assertions::assert_eq; use std::fs; use tempfile::TempDir; @@ -262,7 +263,7 @@ mod tests { fs::write(&rollout_path, "").expect("placeholder rollout file"); let runtime = codex_state::StateRuntime::init( - home.path().to_path_buf(), + codex_state::SqliteConfig::new_for_testing(home.path().abs()), config.default_model_provider_id.clone(), ) .await @@ -331,7 +332,7 @@ mod tests { fs::write(&rollout_path, "").expect("placeholder rollout file"); let runtime = codex_state::StateRuntime::init( - home.path().to_path_buf(), + config.sqlite.clone(), config.default_model_provider_id.clone(), ) .await diff --git a/codex-rs/thread-store/src/local/live_writer.rs b/codex-rs/thread-store/src/local/live_writer.rs index 6202c8fc832d..1457cc9e5538 100644 --- a/codex-rs/thread-store/src/local/live_writer.rs +++ b/codex-rs/thread-store/src/local/live_writer.rs @@ -98,7 +98,7 @@ pub(super) async fn resume_thread( })?; let config = RolloutConfig { codex_home: store.config.codex_home.clone(), - sqlite_home: store.config.sqlite.home().to_path_buf(), + sqlite: store.config.sqlite.clone(), cwd, model_provider_id: params.metadata.model_provider.clone(), generate_memories: matches!(params.metadata.memory_mode, ThreadMemoryMode::Enabled), diff --git a/codex-rs/thread-store/src/local/mod.rs b/codex-rs/thread-store/src/local/mod.rs index 8a2cad525f3d..0ebe34f000c6 100644 --- a/codex-rs/thread-store/src/local/mod.rs +++ b/codex-rs/thread-store/src/local/mod.rs @@ -24,7 +24,6 @@ use codex_protocol::protocol::ThreadHistoryMode; use codex_rollout::RolloutRecorder; use codex_rollout::StateDbHandle; use codex_state::SqliteConfig; -use codex_utils_absolute_path::AbsolutePathBuf; use std::collections::HashMap; use std::collections::hash_map::Entry; use std::path::PathBuf; @@ -128,17 +127,10 @@ pub struct LocalThreadStoreConfig { } impl LocalThreadStoreConfig { - #[expect( - clippy::expect_used, - reason = "resolved Codex configuration paths must be absolute" - )] pub fn from_config(config: &impl codex_rollout::RolloutConfigView) -> Self { Self { codex_home: config.codex_home().to_path_buf(), - sqlite: SqliteConfig::from_sqlite_home( - AbsolutePathBuf::from_absolute_path_checked(config.sqlite_home()) - .expect("sqlite home from resolved config should be absolute"), - ), + sqlite: config.sqlite_config().clone(), default_model_provider_id: config.model_provider_id().to_string(), } } @@ -174,7 +166,7 @@ impl LocalThreadStore { async fn thread_history_db(&self) -> ThreadStoreResult<&sqlx::SqlitePool> { self.thread_history_db .get_or_try_init(|| async { - codex_state::open_thread_history_db(self.config.sqlite.home()).await + codex_state::open_thread_history_db(&self.config.sqlite).await }) .await .map_err(|err| ThreadStoreError::Internal { @@ -517,7 +509,7 @@ mod tests { let home = TempDir::new().expect("temp dir"); let config = test_config(home.path()); let runtime = codex_state::StateRuntime::init( - config.sqlite.home().to_path_buf(), + config.sqlite.clone(), config.default_model_provider_id.clone(), ) .await @@ -552,7 +544,7 @@ mod tests { let home = TempDir::new().expect("temp dir"); let config = test_config(home.path()); let runtime = codex_state::StateRuntime::init( - config.sqlite.home().to_path_buf(), + config.sqlite.clone(), config.default_model_provider_id.clone(), ) .await @@ -587,7 +579,7 @@ mod tests { let home = TempDir::new().expect("temp dir"); let config = test_config(home.path()); let runtime = codex_state::StateRuntime::init( - config.sqlite.home().to_path_buf(), + config.sqlite.clone(), config.default_model_provider_id.clone(), ) .await @@ -655,7 +647,7 @@ mod tests { let home = TempDir::new().expect("temp dir"); let config = test_config(home.path()); let runtime = codex_state::StateRuntime::init( - config.sqlite.home().to_path_buf(), + config.sqlite.clone(), config.default_model_provider_id.clone(), ) .await @@ -744,7 +736,7 @@ mod tests { let home = TempDir::new().expect("temp dir"); let config = test_config(home.path()); let runtime = codex_state::StateRuntime::init( - config.sqlite.home().to_path_buf(), + config.sqlite.clone(), config.default_model_provider_id.clone(), ) .await @@ -780,7 +772,7 @@ mod tests { let home = TempDir::new().expect("temp dir"); let config = test_config(home.path()); let runtime = codex_state::StateRuntime::init( - config.sqlite.home().to_path_buf(), + config.sqlite.clone(), config.default_model_provider_id.clone(), ) .await @@ -820,7 +812,7 @@ mod tests { let home = TempDir::new().expect("temp dir"); let config = test_config(home.path()); let runtime = codex_state::StateRuntime::init( - config.sqlite.home().to_path_buf(), + config.sqlite.clone(), config.default_model_provider_id.clone(), ) .await @@ -864,7 +856,7 @@ mod tests { let home = TempDir::new().expect("temp dir"); let config = test_config(home.path()); let runtime = codex_state::StateRuntime::init( - config.sqlite.home().to_path_buf(), + config.sqlite.clone(), config.default_model_provider_id.clone(), ) .await @@ -919,7 +911,7 @@ mod tests { let external_home = TempDir::new().expect("external temp dir"); let config = test_config(home.path()); let runtime = codex_state::StateRuntime::init( - config.sqlite.home().to_path_buf(), + config.sqlite.clone(), config.default_model_provider_id.clone(), ) .await diff --git a/codex-rs/thread-store/src/local/read_thread.rs b/codex-rs/thread-store/src/local/read_thread.rs index 568ddd93d285..dc07f71e99c1 100644 --- a/codex-rs/thread-store/src/local/read_thread.rs +++ b/codex-rs/thread-store/src/local/read_thread.rs @@ -612,7 +612,7 @@ mod tests { let active_path = write_session_file(home.path(), "2025-01-03T12-00-00", uuid).expect("session file"); let runtime = codex_state::StateRuntime::init( - config.sqlite.home().to_path_buf(), + config.sqlite.clone(), config.default_model_provider_id.clone(), ) .await @@ -771,7 +771,7 @@ mod tests { let rollout_path = write_session_file(home.path(), "2025-01-03T12-00-00", uuid).expect("session file"); let runtime = codex_state::StateRuntime::init( - config.sqlite.home().to_path_buf(), + config.sqlite.clone(), config.default_model_provider_id.clone(), ) .await @@ -816,7 +816,7 @@ mod tests { ) .expect("session file"); let runtime = codex_state::StateRuntime::init( - config.sqlite.home().to_path_buf(), + config.sqlite.clone(), config.default_model_provider_id.clone(), ) .await @@ -859,7 +859,7 @@ mod tests { let rollout_path = write_session_file(home.path(), "2025-01-03T12-00-00", uuid).expect("session file"); let runtime = codex_state::StateRuntime::init( - config.sqlite.home().to_path_buf(), + config.sqlite.clone(), config.default_model_provider_id.clone(), ) .await @@ -900,7 +900,7 @@ mod tests { let rollout_path = write_session_file(home.path(), "2025-01-03T12-00-00", uuid).expect("session file"); let runtime = codex_state::StateRuntime::init( - config.sqlite.home().to_path_buf(), + config.sqlite.clone(), config.default_model_provider_id.clone(), ) .await @@ -934,7 +934,7 @@ mod tests { let home = TempDir::new().expect("temp dir"); let config = test_config(home.path()); let runtime = codex_state::StateRuntime::init( - config.sqlite.home().to_path_buf(), + config.sqlite.clone(), config.default_model_provider_id.clone(), ) .await @@ -1070,7 +1070,7 @@ mod tests { writeln!(file, "{meta}").expect("write session meta"); let runtime = codex_state::StateRuntime::init( - config.sqlite.home().to_path_buf(), + config.sqlite.clone(), config.default_model_provider_id.clone(), ) .await @@ -1124,7 +1124,7 @@ mod tests { write_session_file(home.path(), "2025-01-03T12-00-00", uuid).expect("session file"); let stale_path = external.path().join("missing-rollout.jsonl"); let runtime = codex_state::StateRuntime::init( - config.sqlite.home().to_path_buf(), + config.sqlite.clone(), config.default_model_provider_id.clone(), ) .await @@ -1175,7 +1175,7 @@ mod tests { let stale_path = write_session_file(external.path(), "2025-01-04T12-00-00", other_uuid) .expect("other session file"); let runtime = codex_state::StateRuntime::init( - config.sqlite.home().to_path_buf(), + config.sqlite.clone(), config.default_model_provider_id.clone(), ) .await @@ -1274,7 +1274,7 @@ mod tests { .path() .join(format!("rollout-2025-01-03T12-00-00-{uuid}.jsonl")); let runtime = codex_state::StateRuntime::init( - config.sqlite.home().to_path_buf(), + config.sqlite.clone(), config.default_model_provider_id.clone(), ) .await @@ -1336,7 +1336,7 @@ mod tests { .path() .join(format!("rollout-2025-01-03T12-00-00-{uuid}.jsonl")); let runtime = codex_state::StateRuntime::init( - config.sqlite.home().to_path_buf(), + config.sqlite.clone(), config.default_model_provider_id.clone(), ) .await @@ -1391,7 +1391,7 @@ mod tests { let archived_path = write_archived_session_file(home.path(), "2025-01-03T12-00-00", uuid) .expect("archived session file"); let runtime = codex_state::StateRuntime::init( - config.sqlite.home().to_path_buf(), + config.sqlite.clone(), config.default_model_provider_id.clone(), ) .await diff --git a/codex-rs/thread-store/src/local/search_threads.rs b/codex-rs/thread-store/src/local/search_threads.rs index 6c2769bc4dad..5963d3dcabaf 100644 --- a/codex-rs/thread-store/src/local/search_threads.rs +++ b/codex-rs/thread-store/src/local/search_threads.rs @@ -60,7 +60,7 @@ pub(super) async fn search_threads( let state_db = store.state_db().await; let rollout_config = RolloutConfig { codex_home: store.config.codex_home.clone(), - sqlite_home: store.config.sqlite.home().to_path_buf(), + sqlite: store.config.sqlite.clone(), cwd: store.config.codex_home.clone(), model_provider_id: store.config.default_model_provider_id.clone(), generate_memories: false, diff --git a/codex-rs/thread-store/src/local/thread_history/read_tests.rs b/codex-rs/thread-store/src/local/thread_history/read_tests.rs index f6152bbdc574..1158362e5b25 100644 --- a/codex-rs/thread-store/src/local/thread_history/read_tests.rs +++ b/codex-rs/thread-store/src/local/thread_history/read_tests.rs @@ -507,7 +507,7 @@ async fn store_with_mode(history_mode: ThreadHistoryMode) -> (TempDir, LocalThre ); } let runtime = codex_state::StateRuntime::init( - config.sqlite.home().to_path_buf(), + config.sqlite.clone(), config.default_model_provider_id.clone(), ) .await diff --git a/codex-rs/thread-store/src/local/thread_history_materialization_tests.rs b/codex-rs/thread-store/src/local/thread_history_materialization_tests.rs index 5cdc5e31e791..99516e44c9e3 100644 --- a/codex-rs/thread-store/src/local/thread_history_materialization_tests.rs +++ b/codex-rs/thread-store/src/local/thread_history_materialization_tests.rs @@ -20,22 +20,218 @@ use codex_protocol::protocol::ThreadHistoryMode; use codex_protocol::protocol::ThreadMemoryMode; use codex_protocol::protocol::TurnCompleteEvent; use codex_protocol::protocol::TurnStartedEvent; +use codex_protocol::protocol::UserMessageEvent; +use codex_rollout::RolloutConfig; use codex_rollout::RolloutRecorder; +use codex_rollout::RolloutRecorderParams; use codex_utils_absolute_path::test_support::PathExt; use pretty_assertions::assert_eq; use tempfile::TempDir; use super::super::LocalThreadStore; +use super::super::LocalThreadStoreConfig; use super::super::test_support::test_config; use crate::AppendThreadItemsParams; use crate::CreateThreadParams; use crate::DeleteThreadParams; +use crate::ListThreadsParams; use crate::ListTurnsParams; +use crate::ResumeThreadParams; use crate::SortDirection; use crate::StoredTurnItemsView; +use crate::StoredTurnStatus; use crate::ThreadPersistenceMetadata; +use crate::ThreadSortKey; use crate::ThreadStore; +/// Separate Codex and SQLite homes must work together across startup backfill, +/// thread listing, and projection-backed paginated history reads. +#[tokio::test] +async fn split_homes_support_backfill_listing_and_paginated_history() { + let root = TempDir::new().expect("temp dir"); + let codex_home = root.path().join("codex"); + let sqlite_home = root.path().join("sqlite"); + let thread_id = ThreadId::new(); + let sqlite = codex_state::SqliteConfig::new_for_testing(sqlite_home.as_path().abs()); + let rollout_config = RolloutConfig { + codex_home: codex_home.clone(), + sqlite: sqlite.clone(), + cwd: codex_home.clone(), + model_provider_id: "test-provider".to_string(), + generate_memories: false, + }; + let recorder = RolloutRecorder::new( + &rollout_config, + RolloutRecorderParams::new( + thread_id, + /*forked_from_id*/ None, + /*parent_thread_id*/ None, + SessionSource::Exec, + /*thread_source*/ None, + "test-originator".to_string(), + BaseInstructions::default(), + Vec::new(), + ) + .with_history_mode(ThreadHistoryMode::Paginated) + .with_initial_window_id("window-1".to_string()), + ) + .await + .expect("create paginated rollout"); + recorder + .record_canonical_items(&[RolloutItem::EventMsg(EventMsg::UserMessage( + UserMessageEvent { + message: "existing thread".to_string(), + ..Default::default() + }, + ))]) + .await + .expect("record existing user message"); + recorder.persist().await.expect("persist paginated rollout"); + let rollout_path = recorder.rollout_path().to_path_buf(); + recorder.shutdown().await.expect("close paginated rollout"); + + let runtime = codex_rollout::state_db::try_init(&rollout_config) + .await + .expect("backfill state from Codex home"); + assert!( + runtime + .get_thread(thread_id) + .await + .expect("read backfilled thread") + .is_some(), + "startup backfill should index the rollout" + ); + let store = LocalThreadStore::new( + LocalThreadStoreConfig::from_config(&rollout_config), + Some(runtime), + ); + + let threads = store + .list_threads(ListThreadsParams { + page_size: 10, + cursor: None, + sort_key: ThreadSortKey::CreatedAt, + sort_direction: SortDirection::Desc, + allowed_sources: Vec::new(), + model_providers: None, + cwd_filters: None, + archived: false, + search_term: None, + relation_filter: None, + is_pinned: None, + use_state_db_only: true, + }) + .await + .expect("list backfilled threads"); + assert_eq!(threads.items.len(), 1); + assert_eq!( + ( + threads.items[0].thread_id, + threads.items[0].rollout_path.as_deref(), + threads.items[0].history_mode, + ), + ( + thread_id, + Some(rollout_path.as_path()), + ThreadHistoryMode::Paginated, + ) + ); + + store + .resume_thread(ResumeThreadParams { + thread_id, + rollout_path: Some(rollout_path), + history: None, + include_archived: false, + metadata: ThreadPersistenceMetadata { + cwd: Some(codex_home.clone()), + model_provider: "test-provider".to_string(), + memory_mode: ThreadMemoryMode::Enabled, + }, + }) + .await + .expect("resume backfilled thread"); + store + .append_items(AppendThreadItemsParams { + thread_id, + items: vec![ + turn_started("turn-1"), + completed_item( + thread_id, + "turn-1", + TurnItem::UserMessage(UserMessageItem { + id: "user-1".to_string(), + client_id: None, + content: Vec::new(), + }), + ), + completed_item( + thread_id, + "turn-1", + TurnItem::AgentMessage(AgentMessageItem { + id: "agent-1".to_string(), + content: vec![AgentMessageContent::Text { + text: "done".to_string(), + }], + phase: None, + memory_citation: None, + }), + ), + turn_completed("turn-1"), + ], + }) + .await + .expect("append paginated history"); + + let turns = store + .list_turns(ListTurnsParams { + thread_id, + include_archived: false, + cursor: None, + page_size: 10, + sort_direction: SortDirection::Asc, + items_view: StoredTurnItemsView::Summary, + }) + .await + .expect("list paginated history"); + assert_eq!( + turns + .turns + .iter() + .map(|turn| { + ( + turn.turn_id.as_str(), + turn.status, + turn.items + .iter() + .map(|item| item.item_id.as_str()) + .collect::>(), + ) + }) + .collect::>(), + vec![( + "turn-1", + StoredTurnStatus::Completed, + vec!["user-1", "agent-1"], + )] + ); + + let state_db_path = sqlite.state_db_path(); + let thread_history_db_path = sqlite.thread_history_db_path(); + for sqlite_path in [&state_db_path, &thread_history_db_path] { + assert!( + sqlite_path.exists(), + "expected SQLite database at {}", + sqlite_path.display() + ); + let filename = sqlite_path.file_name().expect("SQLite database filename"); + assert!( + !codex_home.join(filename).exists(), + "SQLite database should not be created under Codex home" + ); + } +} + #[tokio::test] async fn paginated_live_append_materializes_turn_items_and_state() { let home = TempDir::new().expect("temp dir"); @@ -87,9 +283,11 @@ async fn paginated_live_append_materializes_turn_items_and_state() { rollout_line_byte_offsets(rollout_path.as_path(), /*ordinal*/ 1); let (_, turn_end_byte_offset) = rollout_line_byte_offsets(rollout_path.as_path(), /*ordinal*/ 4); - let pool = codex_state::open_thread_history_db(home.path()) - .await - .expect("open thread history db"); + let pool = codex_state::open_thread_history_db(&codex_state::SqliteConfig::new_for_testing( + home.path().abs(), + )) + .await + .expect("open thread history db"); let turn = sqlx::query_as::< _, ( @@ -196,9 +394,11 @@ async fn active_turn_stores_only_its_start_position() { .expect("rollout path"); let (turn_start_byte_offset, _) = rollout_line_byte_offsets(rollout_path.as_path(), /*ordinal*/ 1); - let pool = codex_state::open_thread_history_db(home.path()) - .await - .expect("open thread history db"); + let pool = codex_state::open_thread_history_db(&codex_state::SqliteConfig::new_for_testing( + home.path().abs(), + )) + .await + .expect("open thread history db"); let turn_position = sqlx::query_as::<_, (Option, Option, Option)>( "SELECT rollout_byte_offset, rollout_end_ordinal, rollout_end_byte_offset FROM thread_turns WHERE thread_id = ? AND turn_id = ?", ) @@ -263,9 +463,11 @@ async fn subagent_prefix_advances_projection_without_materializing_history() { .expect("rollout path"); let (child_start_byte_offset, _) = rollout_line_byte_offsets(rollout_path.as_path(), /*ordinal*/ 4); - let pool = codex_state::open_thread_history_db(home.path()) - .await - .expect("open thread history db"); + let pool = codex_state::open_thread_history_db(&codex_state::SqliteConfig::new_for_testing( + home.path().abs(), + )) + .await + .expect("open thread history db"); let turns = sqlx::query_as::<_, (String, i64, Option)>( "SELECT turn_id, rollout_ordinal, rollout_byte_offset FROM thread_turns WHERE thread_id = ?", ) @@ -313,9 +515,11 @@ async fn replayed_item_snapshot_updates_content_without_reordering() { }) .await .expect("append first item snapshot"); - let pool = codex_state::open_thread_history_db(home.path()) - .await - .expect("open thread history db"); + let pool = codex_state::open_thread_history_db(&codex_state::SqliteConfig::new_for_testing( + home.path().abs(), + )) + .await + .expect("open thread history db"); let first_created_at_ms = sqlx::query_scalar::<_, i64>( "SELECT created_at_ms FROM thread_items WHERE thread_id = ? AND turn_id = ? AND item_id = ?", ) @@ -399,9 +603,11 @@ async fn terminal_turn_does_not_change_after_later_records() { .await .expect("append later records"); - let pool = codex_state::open_thread_history_db(home.path()) - .await - .expect("open thread history db"); + let pool = codex_state::open_thread_history_db(&codex_state::SqliteConfig::new_for_testing( + home.path().abs(), + )) + .await + .expect("open thread history db"); let rollout_path = store .live_rollout_path(thread_id) .await @@ -457,7 +663,7 @@ async fn summary_items_use_final_answers_and_ignore_commentary() { let config = test_config(home.path()); let thread_id = ThreadId::default(); let runtime = codex_state::StateRuntime::init( - config.sqlite.home().to_path_buf(), + config.sqlite.clone(), config.default_model_provider_id.clone(), ) .await @@ -582,9 +788,11 @@ async fn next_write_catches_up_unprojected_durable_suffix() { .await .expect("persist session metadata"); - let pool = codex_state::open_thread_history_db(home.path()) - .await - .expect("open thread history db"); + let pool = codex_state::open_thread_history_db(&codex_state::SqliteConfig::new_for_testing( + home.path().abs(), + )) + .await + .expect("open thread history db"); let checkpoint = projection_state(&pool, thread_id).await; store .append_items(AppendThreadItemsParams { @@ -667,9 +875,11 @@ async fn synchronized_catch_up_does_not_replay_old_rows() { .await .expect("append turn start"); - let pool = codex_state::open_thread_history_db(home.path()) - .await - .expect("open thread history db"); + let pool = codex_state::open_thread_history_db(&codex_state::SqliteConfig::new_for_testing( + home.path().abs(), + )) + .await + .expect("open thread history db"); let before = projection_state(&pool, thread_id).await; sqlx::query("UPDATE thread_turns SET status = 'sentinel' WHERE thread_id = ?") .bind(thread_id.to_string()) @@ -705,9 +915,11 @@ async fn catch_up_preserves_trailing_partial_line_boundaries() { .await .expect("persist session metadata"); - let pool = codex_state::open_thread_history_db(home.path()) - .await - .expect("open thread history db"); + let pool = codex_state::open_thread_history_db(&codex_state::SqliteConfig::new_for_testing( + home.path().abs(), + )) + .await + .expect("open thread history db"); let before = projection_state(&pool, thread_id).await; let complete_line = rollout_line(Some(1), turn_started("turn-1")); let partial_line = rollout_line(Some(2), turn_completed("turn-1")); @@ -782,9 +994,11 @@ async fn catch_up_rejects_invalid_complete_suffixes_without_advancing_state() { .await .expect("persist session metadata"); - let pool = codex_state::open_thread_history_db(home.path()) - .await - .expect("open thread history db"); + let pool = codex_state::open_thread_history_db( + &codex_state::SqliteConfig::new_for_testing(home.path().abs()), + ) + .await + .expect("open thread history db"); let before = projection_state(&pool, thread_id).await; let rollout_path = store .live_rollout_path(thread_id) @@ -940,9 +1154,11 @@ async fn blank_and_rejected_rollout_lines_do_not_poison_projection() { .await .expect("project valid retry after rejected line"); - let pool = codex_state::open_thread_history_db(home.path()) - .await - .expect("open thread history db"); + let pool = codex_state::open_thread_history_db(&codex_state::SqliteConfig::new_for_testing( + home.path().abs(), + )) + .await + .expect("open thread history db"); let (expected_start_byte_offset, _) = rollout_line_byte_offsets(rollout_path.as_path(), /*ordinal*/ 1); let start_byte_offset = sqlx::query_scalar::<_, Option>( @@ -980,9 +1196,11 @@ async fn shutdown_materializes_items_queued_without_a_flush() { .await .expect("shutdown live thread"); - let pool = codex_state::open_thread_history_db(home.path()) - .await - .expect("open thread history db"); + let pool = codex_state::open_thread_history_db(&codex_state::SqliteConfig::new_for_testing( + home.path().abs(), + )) + .await + .expect("open thread history db"); let projected_turns = sqlx::query_scalar::<_, i64>( "SELECT COUNT(*) FROM thread_turns WHERE thread_id = ? AND turn_id = ?", ) @@ -1032,9 +1250,11 @@ async fn delete_waits_for_in_flight_projection_before_removing_rows() { .expect("finish in-flight append"); delete.await.expect("join delete").expect("delete thread"); - let pool = codex_state::open_thread_history_db(home.path()) - .await - .expect("open thread history db"); + let pool = codex_state::open_thread_history_db(&codex_state::SqliteConfig::new_for_testing( + home.path().abs(), + )) + .await + .expect("open thread history db"); let counts = sqlx::query_as::<_, (i64, i64, i64)>( r#" SELECT diff --git a/codex-rs/thread-store/src/local/unarchive_thread.rs b/codex-rs/thread-store/src/local/unarchive_thread.rs index 945c06b65ae2..b23a45983368 100644 --- a/codex-rs/thread-store/src/local/unarchive_thread.rs +++ b/codex-rs/thread-store/src/local/unarchive_thread.rs @@ -1,6 +1,3 @@ -use codex_rollout::find_archived_thread_path_by_id_str; -use codex_rollout::rollout_date_parts; - use super::LocalThreadStore; use super::helpers::matching_rollout_file_name; use super::helpers::scoped_rollout_path; @@ -10,6 +7,8 @@ use crate::ReadThreadParams; use crate::StoredThread; use crate::ThreadStoreError; use crate::ThreadStoreResult; +use codex_rollout::find_archived_thread_path_by_id_str; +use codex_rollout::rollout_date_parts; pub(super) async fn unarchive_thread( store: &LocalThreadStore, @@ -94,6 +93,7 @@ mod tests { use chrono::Utc; use codex_protocol::ThreadId; use codex_protocol::protocol::SessionSource; + use codex_utils_absolute_path::test_support::PathExt; use pretty_assertions::assert_eq; use tempfile::TempDir; use uuid::Uuid; @@ -143,7 +143,7 @@ mod tests { let archived_path = write_archived_session_file(home.path(), "2025-01-03T13-00-00", uuid) .expect("archived session file"); let runtime = codex_state::StateRuntime::init( - home.path().to_path_buf(), + codex_state::SqliteConfig::new_for_testing(home.path().abs()), config.default_model_provider_id.clone(), ) .await diff --git a/codex-rs/thread-store/src/local/update_thread_metadata.rs b/codex-rs/thread-store/src/local/update_thread_metadata.rs index 37082bdde25b..50df1e76ff0d 100644 --- a/codex-rs/thread-store/src/local/update_thread_metadata.rs +++ b/codex-rs/thread-store/src/local/update_thread_metadata.rs @@ -842,6 +842,7 @@ mod tests { use codex_protocol::models::PermissionProfile; use codex_protocol::openai_models::ReasoningEffort; use codex_protocol::protocol::ThreadHistoryMode; + use codex_utils_absolute_path::test_support::PathExt; use pretty_assertions::assert_eq; use serde_json::Value; use serde_json::json; @@ -900,7 +901,7 @@ mod tests { write_session_file(home.path(), "2025-01-03T14-20-00", uuid).expect("session file"); let original_rollout = std::fs::read_to_string(&rollout_path).expect("read rollout"); let runtime = codex_state::StateRuntime::init( - home.path().to_path_buf(), + config.sqlite.clone(), config.default_model_provider_id.clone(), ) .await @@ -1009,7 +1010,7 @@ mod tests { .expect("session file"); let original_rollout = std::fs::read_to_string(&path).expect("read rollout"); let runtime = codex_state::StateRuntime::init( - home.path().to_path_buf(), + config.sqlite.clone(), config.default_model_provider_id.clone(), ) .await @@ -1101,7 +1102,7 @@ mod tests { let path = write_session_file(home.path(), "2025-01-03T14-30-00", uuid).expect("session file"); let runtime = codex_state::StateRuntime::init( - home.path().to_path_buf(), + codex_state::SqliteConfig::new_for_testing(home.path().abs()), config.default_model_provider_id.clone(), ) .await @@ -1147,7 +1148,7 @@ mod tests { .expect("session file"); let original_rollout = std::fs::read_to_string(&path).expect("read rollout"); let runtime = codex_state::StateRuntime::init( - home.path().to_path_buf(), + codex_state::SqliteConfig::new_for_testing(home.path().abs()), config.default_model_provider_id.clone(), ) .await @@ -1244,7 +1245,7 @@ mod tests { let path = write_session_file(home.path(), "2025-01-03T18-30-00", uuid).expect("session file"); let runtime = codex_state::StateRuntime::init( - config.sqlite.home().to_path_buf(), + config.sqlite.clone(), config.default_model_provider_id.clone(), ) .await @@ -1349,7 +1350,7 @@ mod tests { let home = TempDir::new().expect("temp dir"); let config = test_config(home.path()); let runtime = codex_state::StateRuntime::init( - config.sqlite.home().to_path_buf(), + config.sqlite.clone(), config.default_model_provider_id.clone(), ) .await @@ -1392,7 +1393,7 @@ mod tests { let home = TempDir::new().expect("temp dir"); let config = test_config(home.path()); let runtime = codex_state::StateRuntime::init( - config.sqlite.home().to_path_buf(), + config.sqlite.clone(), config.default_model_provider_id.clone(), ) .await @@ -1447,7 +1448,7 @@ mod tests { let home = TempDir::new().expect("temp dir"); let config = test_config(home.path()); let runtime = codex_state::StateRuntime::init( - config.sqlite.home().to_path_buf(), + config.sqlite.clone(), config.default_model_provider_id.clone(), ) .await @@ -1505,7 +1506,7 @@ mod tests { let home = TempDir::new().expect("temp dir"); let config = test_config(home.path()); let runtime = codex_state::StateRuntime::init( - config.sqlite.home().to_path_buf(), + config.sqlite.clone(), config.default_model_provider_id.clone(), ) .await @@ -1708,7 +1709,7 @@ mod tests { let home = TempDir::new().expect("temp dir"); let config = test_config(home.path()); let runtime = codex_state::StateRuntime::init( - home.path().to_path_buf(), + codex_state::SqliteConfig::new_for_testing(home.path().abs()), config.default_model_provider_id.clone(), ) .await @@ -1809,7 +1810,7 @@ mod tests { let home = TempDir::new().expect("temp dir"); let config = test_config(home.path()); let runtime = codex_state::StateRuntime::init( - home.path().to_path_buf(), + codex_state::SqliteConfig::new_for_testing(home.path().abs()), config.default_model_provider_id.clone(), ) .await @@ -1852,7 +1853,7 @@ mod tests { let home = TempDir::new().expect("temp dir"); let config = test_config(home.path()); let runtime = codex_state::StateRuntime::init( - home.path().to_path_buf(), + codex_state::SqliteConfig::new_for_testing(home.path().abs()), config.default_model_provider_id.clone(), ) .await @@ -1910,7 +1911,7 @@ mod tests { let home = TempDir::new().expect("temp dir"); let config = test_config(home.path()); let runtime = codex_state::StateRuntime::init( - home.path().to_path_buf(), + codex_state::SqliteConfig::new_for_testing(home.path().abs()), config.default_model_provider_id.clone(), ) .await @@ -1957,7 +1958,7 @@ mod tests { ) .expect("session file"); let runtime = codex_state::StateRuntime::init( - home.path().to_path_buf(), + codex_state::SqliteConfig::new_for_testing(home.path().abs()), config.default_model_provider_id.clone(), ) .await @@ -1997,7 +1998,7 @@ mod tests { write_archived_session_file(home.path(), "2025-01-03T19-30-00", uuid) .expect("archived session file"); let runtime = codex_state::StateRuntime::init( - home.path().to_path_buf(), + codex_state::SqliteConfig::new_for_testing(home.path().abs()), config.default_model_provider_id.clone(), ) .await @@ -2033,7 +2034,7 @@ mod tests { let home = TempDir::new().expect("temp dir"); let config = test_config(home.path()); let runtime = codex_state::StateRuntime::init( - home.path().to_path_buf(), + codex_state::SqliteConfig::new_for_testing(home.path().abs()), config.default_model_provider_id.clone(), ) .await @@ -2103,7 +2104,7 @@ mod tests { let archived_path = write_archived_session_file(home.path(), "2025-01-03T16-00-00", uuid) .expect("archived session file"); let runtime = codex_state::StateRuntime::init( - home.path().to_path_buf(), + codex_state::SqliteConfig::new_for_testing(home.path().abs()), config.default_model_provider_id.clone(), ) .await @@ -2166,7 +2167,7 @@ mod tests { let archived_path = write_archived_session_file(home.path(), "2025-01-03T16-30-00", uuid) .expect("archived session file"); let runtime = codex_state::StateRuntime::init( - home.path().to_path_buf(), + codex_state::SqliteConfig::new_for_testing(home.path().abs()), config.default_model_provider_id.clone(), ) .await diff --git a/codex-rs/tui/src/app/tests.rs b/codex-rs/tui/src/app/tests.rs index 66eae6967103..7d70bd1d9be5 100644 --- a/codex-rs/tui/src/app/tests.rs +++ b/codex-rs/tui/src/app/tests.rs @@ -18,6 +18,7 @@ use crate::app_backtrack::BacktrackSelection; use crate::app_backtrack::BacktrackState; use crate::app_backtrack::user_count; use crate::app_event::HistoryBatchEntryResponse; +use codex_utils_absolute_path::test_support::PathExt; use crate::chatwidget::ChatWidgetInit; use crate::chatwidget::create_initial_user_message; @@ -2249,7 +2250,7 @@ fn update_memory_settings_updates_current_thread_memory_mode() -> Result<()> { let (mut app, _app_event_rx, _op_rx) = Box::pin(make_test_app_with_channels()).await; let codex_home = tempdir()?; app.config.codex_home = codex_home.path().to_path_buf().abs(); - app.config.sqlite_home = codex_home.path().to_path_buf(); + app.config.sqlite = codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()); // Seed the previous setting so this test exercises the thread-mode update path. app.config.memories.generate_memories = true; @@ -2267,7 +2268,7 @@ fn update_memory_settings_updates_current_thread_memory_mode() -> Result<()> { .await; let state_db = codex_state::StateRuntime::init( - codex_home.path().to_path_buf(), + codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()), app.config.model_provider_id.clone(), ) .await @@ -2289,7 +2290,7 @@ async fn reset_memories_clears_local_memory_directories() -> Result<()> { let (mut app, _app_event_rx, _op_rx) = Box::pin(make_test_app_with_channels()).await; let codex_home = tempdir()?; app.config.codex_home = codex_home.path().to_path_buf().abs(); - app.config.sqlite_home = codex_home.path().to_path_buf(); + app.config.sqlite = codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()); let memory_root = codex_home.path().join("memories"); let extensions_root = memory_root.join("extensions"); diff --git a/codex-rs/tui/src/app/tests/advanced_reasoning_tests.rs b/codex-rs/tui/src/app/tests/advanced_reasoning_tests.rs index 662a16bfc113..14e757990ab7 100644 --- a/codex-rs/tui/src/app/tests/advanced_reasoning_tests.rs +++ b/codex-rs/tui/src/app/tests/advanced_reasoning_tests.rs @@ -1,5 +1,6 @@ use super::*; use app_test_support::create_fake_rollout; +use codex_utils_absolute_path::test_support::PathExt; use pretty_assertions::assert_eq; #[tokio::test] @@ -7,7 +8,7 @@ async fn fork_current_session_preserves_conversation_ultra() -> Result<()> { let mut app = make_test_app().await; let codex_home = tempdir()?; app.config.codex_home = codex_home.path().to_path_buf().abs(); - app.config.sqlite_home = codex_home.path().to_path_buf(); + app.config.sqlite = codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()); let source_thread_id = ThreadId::from_string( &create_fake_rollout( codex_home.path(), diff --git a/codex-rs/tui/src/app/tests/safety_buffering.rs b/codex-rs/tui/src/app/tests/safety_buffering.rs index 324cdc2e3a29..f6fb2e21a6ed 100644 --- a/codex-rs/tui/src/app/tests/safety_buffering.rs +++ b/codex-rs/tui/src/app/tests/safety_buffering.rs @@ -4,6 +4,7 @@ use crate::app::session_lifecycle::ThreadAttachPresentation; use crate::chatwidget::UserMessage; use codex_app_server_client::AppServerEvent; use codex_app_server_protocol::ModelSafetyBufferingUpdatedNotification; +use codex_utils_absolute_path::test_support::PathExt; use core_test_support::responses; use core_test_support::responses::ev_assistant_message; use core_test_support::responses::ev_completed; @@ -268,7 +269,7 @@ goals = true ), )?; app.config.codex_home = codex_home.path().to_path_buf().abs(); - app.config.sqlite_home = codex_home.path().to_path_buf(); + app.config.sqlite = codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()); app.config.model = Some(CURRENT_MODEL.to_string()); app.config.model_provider_id = MODEL_PROVIDER_ID.to_string(); app.config.model_provider = ModelProviderInfo { @@ -320,7 +321,7 @@ goals = true } let state_db = codex_state::StateRuntime::init( - app.config.sqlite_home.clone(), + app.config.sqlite.clone(), app.config.model_provider_id.clone(), ) .await diff --git a/codex-rs/tui/src/app/tests/session_lifecycle_requests.rs b/codex-rs/tui/src/app/tests/session_lifecycle_requests.rs index 6f705358741c..41161eb4ee52 100644 --- a/codex-rs/tui/src/app/tests/session_lifecycle_requests.rs +++ b/codex-rs/tui/src/app/tests/session_lifecycle_requests.rs @@ -8,6 +8,7 @@ use codex_app_server_protocol::JSONRPCError; use codex_app_server_protocol::JSONRPCMessage; use codex_app_server_protocol::JSONRPCResponse; use codex_protocol::AgentPath; +use codex_state::SqliteConfig; use futures::SinkExt; use futures::StreamExt; use pretty_assertions::assert_eq; @@ -152,7 +153,7 @@ fn fresh_session_applies_requested_name() -> Result<()> { let mut app = make_test_app().await; let codex_home = tempdir()?; app.config.codex_home = codex_home.path().to_path_buf().abs(); - app.config.sqlite_home = codex_home.path().to_path_buf(); + app.config.sqlite = SqliteConfig::new_for_testing(codex_home.path().abs()); let (mut app_server, requests, proxy) = start_recording_app_server(&app.config).await?; let mut tui = crate::tui::test_support::make_test_tui()?; @@ -208,7 +209,8 @@ fn session_lifecycle_avoids_redundant_subagent_metadata_reads() -> Result<()> { let mut app = make_test_app().await; let codex_home = tempdir()?; app.config.codex_home = codex_home.path().to_path_buf().abs(); - app.config.sqlite_home = codex_home.path().to_path_buf(); + app.config.sqlite = + codex_state::SqliteConfig::new_for_testing(codex_home.path().abs()); let root_timestamp = "2026-01-01T00-00-00"; let root_thread_id = ThreadId::from_string( &create_fake_rollout( diff --git a/codex-rs/tui/src/chatwidget/tests/helpers.rs b/codex-rs/tui/src/chatwidget/tests/helpers.rs index fe392481f645..21928bea9d93 100644 --- a/codex-rs/tui/src/chatwidget/tests/helpers.rs +++ b/codex-rs/tui/src/chatwidget/tests/helpers.rs @@ -1,6 +1,7 @@ use super::*; use codex_app_server_protocol::ImageGenerationItem; use codex_app_server_protocol::PluginAvailability; +use codex_utils_absolute_path::test_support::PathExt; use pretty_assertions::assert_eq; pub(super) async fn test_config() -> Config { @@ -15,7 +16,7 @@ pub(super) async fn test_config() -> Config { .await .expect("config"); config.codex_home = codex_home.abs(); - config.sqlite_home = codex_home.clone(); + config.sqlite = codex_state::SqliteConfig::new_for_testing(codex_home.as_path().abs()); config.log_dir = codex_home.join("log"); config.cwd = PathBuf::from(test_path_display("/tmp/project")).abs(); config.config_layer_stack = ConfigLayerStack::default(); diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index 83eb28f8a654..ac7b57bb4836 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -290,15 +290,7 @@ async fn init_state_db_for_app_server_target( match app_server_target { AppServerTarget::Embedded => state_db::try_init(config).await.map(Some).map_err(|err| { let database_path = codex_state::runtime_db_path_for_corruption_error(&err) - .unwrap_or_else(|| { - codex_state::SqliteConfig::from_sqlite_home( - AbsolutePathBuf::resolve_path_against_base( - &config.sqlite_home, - &config.codex_home, - ), - ) - .state_db_path() - }); + .unwrap_or_else(|| config.sqlite_config().state_db_path()); std::io::Error::other(LocalStateDbStartupError::new( database_path, format!("{err:#}"), @@ -3024,7 +3016,7 @@ mod tests { std::fs::write(&rollout_path, "")?; let state_runtime = codex_state::StateRuntime::init( - config.codex_home.to_path_buf(), + codex_state::SqliteConfig::new_for_testing(config.codex_home.as_path().abs()), config.model_provider_id.clone(), ) .await @@ -3111,7 +3103,9 @@ mod tests { let mut config = build_config(&temp_dir).await?; let occupied_sqlite_home = temp_dir.path().join("sqlite-home"); std::fs::write(&occupied_sqlite_home, "occupied")?; - config.sqlite_home = occupied_sqlite_home.clone(); + let sqlite = + codex_state::SqliteConfig::new_for_testing(occupied_sqlite_home.as_path().abs()); + config.sqlite = sqlite.clone(); let err = match init_state_db_for_app_server_target(&config, &AppServerTarget::Embedded).await { @@ -3125,9 +3119,7 @@ mod tests { assert_eq!( startup_error.state_db_path(), - codex_state::SqliteConfig::new_for_testing(config.sqlite_home.as_path().abs()) - .state_db_path() - .as_path() + sqlite.state_db_path().as_path() ); assert!( startup_error @@ -3145,10 +3137,10 @@ mod tests { let mut config = build_config(&temp_dir).await?; let sqlite_home = temp_dir.path().join("sqlite-home"); std::fs::create_dir_all(&sqlite_home)?; - let logs_db_path = - codex_state::SqliteConfig::new_for_testing(sqlite_home.as_path().abs()).logs_db_path(); + let sqlite = codex_state::SqliteConfig::new_for_testing(sqlite_home.as_path().abs()); + let logs_db_path = sqlite.logs_db_path(); std::fs::write(&logs_db_path, "not a sqlite database")?; - config.sqlite_home = sqlite_home; + config.sqlite = sqlite; let err = match init_state_db_for_app_server_target(&config, &AppServerTarget::Embedded).await { diff --git a/codex-rs/tui/src/session_resume.rs b/codex-rs/tui/src/session_resume.rs index 2e86baed4bc1..4d84497e52e6 100644 --- a/codex-rs/tui/src/session_resume.rs +++ b/codex-rs/tui/src/session_resume.rs @@ -243,6 +243,7 @@ async fn read_rollout_resume_state(path: &Path) -> io::Result