Honor the configured SQLite home across state consumers (#34994)

## Why

Codex and SQLite data can use separate home directories, but state consumers
could reconstruct database paths from the Codex home instead of consistently
using the resolved SQLite configuration.

## What changed

- Pass `SqliteConfig` through the core, rollout, state runtime, and thread store
  instead of passing a directory and rebuilding the configuration downstream.
- Use that shared configuration for state, logs, memories, goals, and paginated
  thread-history database access, including integrity checks and cleanup.
- Reject state database handles whose SQLite configuration does not match the
  requesting store.

## Testing

Add coverage with separate Codex and SQLite homes that verifies startup
backfill, thread listing, and paginated history all use the configured SQLite
directory.

GitOrigin-RevId: 1de1cdd1d6ff1d70bbb6c360c8352e6543fb8ebf
This commit is contained in:
Adam Perry @ OpenAI
2026-07-23 19:19:35 +00:00
committed by copyberry
parent 74e9d7efc4
commit c769a05340
76 changed files with 1383 additions and 702 deletions
+3
View File
@@ -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",
+1
View File
@@ -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 }
+8 -5
View File
@@ -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,
@@ -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> {
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]
@@ -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> {
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]
@@ -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> {
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<AuthManager> {
+3 -11
View File
@@ -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())
{
@@ -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?
@@ -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<StateRuntime>, codex_home: &Path) ->
}
async fn init_state_db(codex_home: &Path) -> Result<Arc<StateRuntime>> {
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?;
@@ -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())
@@ -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?;
@@ -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,
@@ -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(
@@ -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,
@@ -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<Arc<StateRuntime>> {
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?;
@@ -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<Arc<StateRuntime>> {
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?;
@@ -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(),
@@ -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?;
+16 -11
View File
@@ -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<String>,
integrity_failures: &mut Vec<String>,
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"));
}
+4 -11
View File
@@ -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(
+2 -5
View File
@@ -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?;
+2 -4
View File
@@ -16,8 +16,7 @@ fn codex_command(codex_home: &Path) -> Result<assert_cmd::Command> {
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();
+1
View File
@@ -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 }
+1
View File
@@ -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;
+2 -2
View File
@@ -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);
+14 -12
View File
@@ -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<PathBuf> {
fn resolve_sqlite_home_env(resolved_cwd: &Path) -> Option<AbsolutePathBuf> {
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<MultiAgentVersion> {
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
+2 -2
View File
@@ -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 {
+1 -1
View File
@@ -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
@@ -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
+3 -10
View File
@@ -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;
+1
View File
@@ -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"] }
@@ -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<Arc<codex_state::StateRuntime>> {
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<ThreadId> {
@@ -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,
+6 -2
View File
@@ -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<TempDir>) -> anyhow::Result<Arc<codex_state::StateRuntime>> {
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)
}
+1
View File
@@ -47,4 +47,5 @@ uuid = { workspace = true }
zstd = { workspace = true }
[dev-dependencies]
codex-utils-absolute-path = { workspace = true }
pretty_assertions = { workspace = true }
+2 -1
View File
@@ -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,
+10 -9
View File
@@ -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<T: RolloutConfigView + ?Sized> 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<T: RolloutConfigView + ?Sized> RolloutConfigView for Arc<T> {
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 {
+25 -12
View File
@@ -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;
+7 -5
View File
@@ -437,6 +437,7 @@ impl RolloutRecorder {
search_term: Option<&str>,
) -> std::io::Result<ThreadsPage> {
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<Option<PathBuf>> {
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,
+8 -7
View File
@@ -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
+25 -35
View File
@@ -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<StateDbHandle> {
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<StateDbHandle> {
/// tracing or UI setup has completed.
pub async fn try_init(config: &impl RolloutConfigView) -> anyhow::Result<StateDbHandle> {
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<StateDbHandle> {
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<StateDbHandle> {
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<i64>,
) -> anyhow::Result<StateDbHandle> {
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<StateDbHandle> {
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<StateDbHand
return None;
}
let runtime = match codex_state::StateRuntime::init(
config.sqlite_home().to_path_buf(),
config.sqlite_config().clone(),
config.model_provider_id().to_string(),
)
.await
@@ -243,7 +231,7 @@ pub async fn get_state_db(config: &impl RolloutConfigView) -> Option<StateDbHand
return None;
}
};
require_backfill_complete(runtime, config.sqlite_home()).await
require_backfill_complete(runtime, config.sqlite_config().home()).await
}
/// Build a SQLite telemetry recorder backed by an OTEL metrics client.
@@ -307,7 +295,7 @@ pub fn normalize_cwd_for_state_db(cwd: &Path) -> 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<Vec<ThreadId>> {
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<codex_state::ThreadsPage> {
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);
+68 -14
View File
@@ -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()),
+19 -9
View File
@@ -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
+6 -7
View File
@@ -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<Vec<ThreadStateAuditRow>> {
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<Vec<ThreadStateAuditRow>> {
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
+5 -1
View File
@@ -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?;
+25 -12
View File
@@ -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 {
+7 -3
View File
@@ -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()
+23 -30
View File
@@ -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<sqlx::SqlitePool>,
logs_pool: Arc<sqlx::SqlitePool>,
@@ -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<Arc<Self>> {
Self::init_inner(
codex_home,
default_provider,
/*telemetry_override*/ None,
)
.await
pub async fn init(sqlite: SqliteConfig, default_provider: String) -> anyhow::Result<Arc<Self>> {
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<Arc<Self>> {
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<Arc<Self>> {
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<bool> {
let sqlite = SqliteConfig::from_sqlite_home(AbsolutePathBuf::try_from(sqlite_home)?);
pub async fn clear_memory_data_in_sqlite_home(sqlite: &SqliteConfig) -> anyhow::Result<bool> {
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<SqlitePool> {
let sqlite = SqliteConfig::from_sqlite_home(AbsolutePathBuf::try_from(sqlite_home)?);
pub async fn open_thread_history_db(sqlite: &SqliteConfig) -> anyhow::Result<SqlitePool> {
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<Vec<String>> {
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<Vec<String>> {
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,
)
+20 -11
View File
@@ -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)
@@ -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(
+9 -8
View File
@@ -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> {
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
+126 -63
View File
@@ -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<LogEntry> = (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<LogEntry> = (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<LogEntry> = (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(&[])
+235 -117
View File
@@ -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)
+6 -1
View File
@@ -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,
};
+18 -9
View File
@@ -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",
+149 -71
View File
@@ -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 =
+2 -1
View File
@@ -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<String>, 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,
+1
View File
@@ -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"] }
@@ -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
@@ -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),
@@ -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')",
@@ -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
@@ -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),
+11 -19
View File
@@ -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
+12 -12
View File
@@ -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
@@ -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,
@@ -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
@@ -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::<Vec<_>>(),
)
})
.collect::<Vec<_>>(),
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<i64>, Option<i64>, Option<i64>)>(
"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<i64>)>(
"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<i64>>(
@@ -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
@@ -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
@@ -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
+4 -3
View File
@@ -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");
@@ -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(),
@@ -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
@@ -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(
+2 -1
View File
@@ -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();
+9 -17
View File
@@ -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 {
+7 -4
View File
@@ -243,6 +243,7 @@ async fn read_rollout_resume_state(path: &Path) -> io::Result<RolloutResumeState
#[cfg(test)]
mod tests {
use super::*;
use codex_utils_absolute_path::test_support::PathExt;
use pretty_assertions::assert_eq;
use tempfile::TempDir;
@@ -435,10 +436,12 @@ mod tests {
),
],
)?;
let state_runtime =
StateRuntime::init(temp_dir.path().to_path_buf(), "test-provider".to_string())
.await
.map_err(std::io::Error::other)?;
let state_runtime = StateRuntime::init(
codex_state::SqliteConfig::new_for_testing(temp_dir.path().abs()),
"test-provider".to_string(),
)
.await
.map_err(std::io::Error::other)?;
let created_at = chrono::DateTime::parse_from_rfc3339("2025-01-05T12:00:00Z")
.expect("timestamp should parse")
.with_timezone(&chrono::Utc);