From 94a4bdb297a051d2f42daa8ec2a0f90ce1a9ff02 Mon Sep 17 00:00:00 2001 From: mcp-devbox Date: Tue, 25 Aug 2026 18:41:44 +0000 Subject: [PATCH 1/7] fix(state): do not gate startup on locked logs db --- .../src/request_processors/thread_delete.rs | 9 + codex-rs/state/src/runtime.rs | 300 ++++++++++++++++-- codex-rs/state/src/runtime/logs.rs | 12 +- codex-rs/state/src/runtime/recovery.rs | 27 ++ codex-rs/state/src/runtime/threads.rs | 18 +- 5 files changed, 331 insertions(+), 35 deletions(-) diff --git a/codex-rs/app-server/src/request_processors/thread_delete.rs b/codex-rs/app-server/src/request_processors/thread_delete.rs index c83140d7ad70..52ea87177b91 100644 --- a/codex-rs/app-server/src/request_processors/thread_delete.rs +++ b/codex-rs/app-server/src/request_processors/thread_delete.rs @@ -40,6 +40,15 @@ impl ThreadRequestProcessor { self.validate_root_thread_delete(thread_id, thread_ids.len() > 1) .await?; + if let Some(state_db) = self.state_db.as_ref() { + state_db + .ensure_strict_thread_delete_available() + .map_err(|err| { + internal_error(format!( + "failed to prepare app-server state deletion for {thread_id}: {err}" + )) + })?; + } for thread_id_to_delete in thread_ids.iter().copied() { self.prepare_thread_for_delete(thread_id_to_delete).await; } diff --git a/codex-rs/state/src/runtime.rs b/codex-rs/state/src/runtime.rs index e7fa3cffefd4..66475644f40c 100644 --- a/codex-rs/state/src/runtime.rs +++ b/codex-rs/state/src/runtime.rs @@ -69,6 +69,7 @@ pub use recovery::RuntimeDbBackup; pub(super) use recovery::RuntimeDbInitError; pub use recovery::backup_runtime_db_for_fresh_start; pub use recovery::is_sqlite_corruption_error; +use recovery::is_sqlite_lock_error; pub use recovery::runtime_db_path_for_corruption_error; pub use recovery::sqlite_error_detail_is_corruption; pub use recovery::sqlite_error_detail_is_lock; @@ -89,7 +90,7 @@ pub struct StateRuntime { sqlite: SqliteConfig, default_provider: String, pool: Arc, - logs_pool: Arc, + logs_pool: Option>, thread_goals: GoalStore, memories: MemoryStore, thread_queue: SqliteQueueStore, @@ -147,7 +148,19 @@ impl StateRuntime { .open_logs_db(&logs_migrator, telemetry_override) .await { - Ok(db) => Arc::new(db), + Ok(db) => Some(Arc::new(db)), + Err(err) if is_sqlite_lock_error(&err) => { + warn!( + "logs db at {} is locked; continuing without persistent log storage: {err}", + logs_path.display() + ); + crate::telemetry::record_fallback( + "state_runtime_init", + "logs_db_locked", + telemetry_override, + ); + None + } Err(err) => { warn!("failed to open logs db at {}: {err}", logs_path.display()); close_sqlite_pools(&[pool.as_ref()]).await; @@ -161,7 +174,7 @@ impl StateRuntime { Ok(db) => Arc::new(db), Err(err) => { warn!("failed to open goals db at {}: {err}", goals_path.display()); - close_sqlite_pools(&[pool.as_ref(), logs_pool.as_ref()]).await; + close_sqlite_pools_with_optional_logs(&[pool.as_ref()], logs_pool.as_deref()).await; return Err(err); } }; @@ -175,7 +188,11 @@ impl StateRuntime { "failed to open memories db at {}: {err}", memories_path.display() ); - close_sqlite_pools(&[pool.as_ref(), logs_pool.as_ref(), goals_pool.as_ref()]).await; + close_sqlite_pools_with_optional_logs( + &[pool.as_ref(), goals_pool.as_ref()], + logs_pool.as_deref(), + ) + .await; return Err(err); } }; @@ -186,12 +203,10 @@ impl StateRuntime { Ok(db) => Arc::new(db), Err(err) => { warn!("failed to open queue db at {}: {err}", queue_path.display()); - close_sqlite_pools(&[ - pool.as_ref(), - logs_pool.as_ref(), - goals_pool.as_ref(), - memories_pool.as_ref(), - ]) + close_sqlite_pools_with_optional_logs( + &[pool.as_ref(), goals_pool.as_ref(), memories_pool.as_ref()], + logs_pool.as_deref(), + ) .await; return Err(err); } @@ -206,13 +221,15 @@ impl StateRuntime { &backfill_state_result, ); if let Err(err) = backfill_state_result { - close_sqlite_pools(&[ - pool.as_ref(), - logs_pool.as_ref(), - goals_pool.as_ref(), - memories_pool.as_ref(), - queue_pool.as_ref(), - ]) + close_sqlite_pools_with_optional_logs( + &[ + pool.as_ref(), + goals_pool.as_ref(), + memories_pool.as_ref(), + queue_pool.as_ref(), + ], + logs_pool.as_deref(), + ) .await; return Err(err); } @@ -235,13 +252,15 @@ impl StateRuntime { match thread_timestamp_millis_result { Ok(value) => value, Err(err) => { - close_sqlite_pools(&[ - pool.as_ref(), - logs_pool.as_ref(), - goals_pool.as_ref(), - memories_pool.as_ref(), - queue_pool.as_ref(), - ]) + close_sqlite_pools_with_optional_logs( + &[ + pool.as_ref(), + goals_pool.as_ref(), + memories_pool.as_ref(), + queue_pool.as_ref(), + ], + logs_pool.as_deref(), + ) .await; return Err(err); } @@ -259,7 +278,9 @@ impl StateRuntime { thread_updated_at_millis: Arc::new(AtomicI64::new(thread_updated_at_millis)), thread_recency_at_millis: Arc::new(AtomicI64::new(thread_recency_at_millis)), }); - if let Err(err) = runtime.run_logs_startup_maintenance().await { + if runtime.logs_pool.is_some() + && let Err(err) = runtime.run_logs_startup_maintenance().await + { warn!( "failed to run startup maintenance for logs db at {}: {err}", logs_path.display(), @@ -286,12 +307,28 @@ impl StateRuntime { &self.thread_queue } + /// Verify that strict thread deletion can reach every persistent state store. + pub fn ensure_strict_thread_delete_available(&self) -> anyhow::Result<()> { + self.logs_pool().map(|_| ()) + } + + fn logs_pool(&self) -> anyhow::Result<&SqlitePool> { + self.logs_pool.as_deref().ok_or_else(|| { + anyhow::anyhow!( + "persistent log store at {} is unavailable because it was locked during startup; restart Codex after the lock is released", + self.sqlite.logs_db_path().display() + ) + }) + } + /// Close all SQLite pools and wait for outstanding pool workers to exit. pub async fn close(&self) { self.thread_queue.close().await; self.memories.close().await; self.thread_goals.close().await; - self.logs_pool.close().await; + if let Some(logs_pool) = self.logs_pool.as_ref() { + logs_pool.close().await; + } self.pool.close().await; } @@ -317,6 +354,16 @@ async fn close_sqlite_pools(pools: &[&SqlitePool]) { } } +async fn close_sqlite_pools_with_optional_logs( + pools: &[&SqlitePool], + logs_pool: Option<&SqlitePool>, +) { + close_sqlite_pools(pools).await; + if let Some(logs_pool) = logs_pool { + logs_pool.close().await; + } +} + /// Open and migrate the rebuildable paginated thread-history database. pub async fn open_thread_history_db(sqlite: &SqliteConfig) -> anyhow::Result { let migrator = runtime_thread_history_migrator(); @@ -426,14 +473,18 @@ mod tests { use super::sqlite_integrity_check; use super::test_support::test_thread_metadata; use super::test_support::unique_temp_dir; + use crate::DB_FALLBACK_METRIC; use crate::DB_INIT_METRIC; use crate::DbTelemetry; + use crate::LogQuery; use crate::migrations::STATE_MIGRATOR; use codex_protocol::ThreadId; use codex_utils_absolute_path::test_support::PathExt; use pretty_assertions::assert_eq; + use sqlx::Sqlite; use sqlx::SqlitePool; use sqlx::migrate::MigrateError; + use sqlx::pool::PoolConnection; use std::collections::BTreeMap; use std::collections::BTreeSet; use std::path::Path; @@ -502,6 +553,38 @@ mod tests { .expect("open sqlite pool") } + async fn hold_logs_write_lock( + sqlite: &crate::SqliteConfig, + ) -> (SqlitePool, PoolConnection) { + let pool = sqlite + .open_read_write_pool(sqlite.logs_db_path().as_path()) + .await + .expect("open logs lock-holder pool"); + sqlx::query("CREATE TABLE lock_holder (value INTEGER NOT NULL)") + .execute(&pool) + .await + .expect("create logs lock-holder table"); + let mut blocker = pool.acquire().await.expect("acquire logs lock holder"); + sqlx::query("BEGIN IMMEDIATE") + .execute(&mut *blocker) + .await + .expect("hold logs database write lock"); + sqlx::query("INSERT INTO lock_holder (value) VALUES (1)") + .execute(&mut *blocker) + .await + .expect("keep logs write transaction active"); + (pool, blocker) + } + + async fn release_logs_write_lock(mut blocker: PoolConnection, pool: SqlitePool) { + sqlx::query("ROLLBACK") + .execute(&mut *blocker) + .await + .expect("release logs database write lock"); + drop(blocker); + pool.close().await; + } + #[tokio::test] async fn sqlite_integrity_check_can_be_interrupted_and_retried() { let codex_home = unique_temp_dir(); @@ -679,6 +762,171 @@ mod tests { let _ = tokio::fs::remove_dir_all(codex_home).await; } + #[tokio::test] + async fn transient_logs_lock_recovers_without_degradation() { + let codex_home = unique_temp_dir(); + tokio::fs::create_dir_all(&codex_home) + .await + .expect("create codex home"); + let sqlite = crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()); + let telemetry = TestTelemetry::default(); + let (blocker_pool, blocker) = hold_logs_write_lock(&sqlite).await; + + let init = StateRuntime::init_with_telemetry_for_tests( + sqlite.clone(), + "test-provider".to_string(), + &telemetry, + ); + let release = async move { + tokio::time::sleep(std::time::Duration::from_millis(100)).await; + release_logs_write_lock(blocker, blocker_pool).await; + }; + let (runtime, ()) = tokio::join!(init, release); + let runtime = runtime.expect("transient logs lock should clear within the busy timeout"); + + runtime + .query_logs(&LogQuery::default()) + .await + .expect("transient lock should preserve persistent log storage"); + assert!( + !telemetry.counters().iter().any(|event| { + event.name == DB_FALLBACK_METRIC + && event.tags.get("caller").map(String::as_str) == Some("state_runtime_init") + }), + "transient logs lock should not enter degraded mode" + ); + + runtime.close().await; + let _ = tokio::fs::remove_dir_all(codex_home).await; + } + + #[tokio::test] + async fn locked_logs_db_does_not_gate_state_but_blocks_strict_thread_delete() { + let codex_home = unique_temp_dir(); + tokio::fs::create_dir_all(&codex_home) + .await + .expect("create codex home"); + let sqlite = crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()); + let telemetry = TestTelemetry::default(); + let (blocker_pool, blocker) = hold_logs_write_lock(&sqlite).await; + + let runtime = StateRuntime::init_with_telemetry_for_tests( + sqlite.clone(), + "test-provider".to_string(), + &telemetry, + ) + .await + .expect("locked logs db should not gate state runtime initialization"); + runtime + .ensure_strict_thread_delete_available() + .expect_err("strict thread deletion should preflight the persistent log store"); + + let log_err = runtime + .query_logs(&LogQuery::default()) + .await + .expect_err("degraded runtime should report unavailable persistent logs"); + assert!( + log_err.to_string().contains("log store"), + "unexpected degraded log error: {log_err:#}" + ); + + let thread_id = + ThreadId::from_string("00000000-0000-0000-0000-000000000355").expect("valid thread id"); + runtime + .upsert_thread(&test_thread_metadata( + &codex_home, + thread_id, + codex_home.clone(), + )) + .await + .expect("core thread state should remain writable while logs are degraded"); + runtime + .thread_goals() + .replace_thread_goal( + thread_id, + "preserve me", + crate::ThreadGoalStatus::Active, + /*token_budget*/ None, + ) + .await + .expect("thread goal should be stored before failed strict deletion"); + + let delete_err = runtime + .delete_thread(thread_id) + .await + .expect_err("strict deletion must not succeed without the persistent log store"); + assert!( + delete_err.to_string().contains("log store"), + "unexpected strict deletion error: {delete_err:#}" + ); + assert!( + runtime + .get_thread(thread_id) + .await + .expect("read thread after failed strict deletion") + .is_some(), + "failed strict deletion must leave core thread state intact" + ); + assert!( + runtime + .thread_goals() + .get_thread_goal(thread_id) + .await + .expect("read thread goal after failed strict deletion") + .is_some(), + "failed strict deletion must leave associated state intact" + ); + + assert!( + telemetry.counters().iter().any(|event| { + event.name == DB_FALLBACK_METRIC + && event.tags.get("caller").map(String::as_str) == Some("state_runtime_init") + && event.tags.get("reason").map(String::as_str) == Some("logs_db_locked") + }), + "degraded logs startup should emit fallback telemetry" + ); + + release_logs_write_lock(blocker, blocker_pool).await; + runtime + .query_logs(&LogQuery::default()) + .await + .expect_err("degraded runtime should require a restart after the lock is released"); + + runtime.close().await; + let recovered = StateRuntime::init(sqlite, "test-provider".to_string()) + .await + .expect("persistent log store should recover after restart"); + recovered + .query_logs(&LogQuery::default()) + .await + .expect("persistent log store should be queryable after restart"); + recovered.close().await; + let _ = tokio::fs::remove_dir_all(codex_home).await; + } + + #[tokio::test] + async fn corrupt_logs_db_remains_fatal_for_existing_recovery() { + let codex_home = unique_temp_dir(); + tokio::fs::create_dir_all(&codex_home) + .await + .expect("create codex home"); + let sqlite = crate::SqliteConfig::new_for_testing(codex_home.as_path().abs()); + tokio::fs::write(sqlite.logs_db_path(), b"not a sqlite database") + .await + .expect("write corrupt logs db"); + + let err = match StateRuntime::init(sqlite, "test-provider".to_string()).await { + Ok(_) => panic!("corrupt logs db should stay on the recovery path"), + Err(err) => err, + }; + assert!( + super::is_sqlite_corruption_error(&err), + "corrupt logs db should remain classified as corruption: {err:#}" + ); + + let _ = tokio::fs::remove_dir_all(codex_home).await; + } + #[tokio::test] async fn init_restores_independent_thread_timestamp_maxima() { let codex_home = unique_temp_dir(); diff --git a/codex-rs/state/src/runtime/logs.rs b/codex-rs/state/src/runtime/logs.rs index d2525480c7f5..b886f43a5113 100644 --- a/codex-rs/state/src/runtime/logs.rs +++ b/codex-rs/state/src/runtime/logs.rs @@ -13,7 +13,7 @@ impl StateRuntime { return Ok(()); } - let mut tx = self.logs_pool.begin().await?; + let mut tx = self.logs_pool()?.begin().await?; let mut builder = QueryBuilder::::new( "INSERT INTO logs (ts, ts_nanos, level, target, feedback_log_body, thread_id, process_uuid, module_path, file, line, estimated_bytes) ", ); @@ -283,7 +283,7 @@ WHERE id IN ( pub(crate) async fn delete_logs_before(&self, cutoff_ts: i64) -> anyhow::Result { let result = sqlx::query("DELETE FROM logs WHERE ts < ?") .bind(cutoff_ts) - .execute(self.logs_pool.as_ref()) + .execute(self.logs_pool()?) .await?; Ok(result.rows_affected()) } @@ -299,7 +299,7 @@ WHERE id IN ( // PASSIVE checkpoints copy whatever is immediately available and skip // frames that would require waiting on active readers or writers. sqlx::query("PRAGMA wal_checkpoint(PASSIVE)") - .execute(self.logs_pool.as_ref()) + .execute(self.logs_pool()?) .await?; Ok(()) } @@ -321,7 +321,7 @@ WHERE id IN ( let rows = builder .build_query_as::() - .fetch_all(self.logs_pool.as_ref()) + .fetch_all(self.logs_pool()?) .await?; Ok(rows) } @@ -402,7 +402,7 @@ WHERE cumulative_estimated_bytes <= builder.push(" ORDER BY ts DESC, ts_nanos DESC, id DESC"); let rows = builder .build_query_as::() - .fetch_all(self.logs_pool.as_ref()) + .fetch_all(self.logs_pool()?) .await?; let mut lines = Vec::new(); @@ -435,7 +435,7 @@ WHERE cumulative_estimated_bytes <= let mut builder = QueryBuilder::::new("SELECT MAX(id) AS max_id FROM logs WHERE 1 = 1"); push_log_filters(&mut builder, query); - let row = builder.build().fetch_one(self.logs_pool.as_ref()).await?; + let row = builder.build().fetch_one(self.logs_pool()?).await?; let max_id: Option = row.try_get("max_id")?; Ok(max_id.unwrap_or(0)) } diff --git a/codex-rs/state/src/runtime/recovery.rs b/codex-rs/state/src/runtime/recovery.rs index d9cf0bcdc9b8..e05aa4e36700 100644 --- a/codex-rs/state/src/runtime/recovery.rs +++ b/codex-rs/state/src/runtime/recovery.rs @@ -104,6 +104,10 @@ pub fn is_sqlite_corruption_error(err: &anyhow::Error) -> bool { err.chain().any(sqlite_error_source_is_corruption) } +pub(super) fn is_sqlite_lock_error(err: &anyhow::Error) -> bool { + err.chain().any(sqlite_error_source_is_lock) +} + fn sqlite_error_source_is_corruption(source: &(dyn std::error::Error + 'static)) -> bool { let Some(err) = source.downcast_ref::() else { return false; @@ -117,6 +121,19 @@ fn sqlite_error_source_is_corruption(source: &(dyn std::error::Error + 'static)) .is_some_and(sqlite_database_code_is_corruption) } +fn sqlite_error_source_is_lock(source: &(dyn std::error::Error + 'static)) -> bool { + let Some(err) = source.downcast_ref::() else { + return false; + }; + let sqlx::Error::Database(database_error) = err else { + return false; + }; + sqlite_error_detail_is_lock(database_error.message()) + || database_error + .code() + .is_some_and(sqlite_database_code_is_lock) +} + fn sqlite_database_code_is_corruption(code: Cow<'_, str>) -> bool { matches!( code.as_ref().to_ascii_lowercase().as_str(), @@ -124,6 +141,16 @@ fn sqlite_database_code_is_corruption(code: Cow<'_, str>) -> bool { ) } +fn sqlite_database_code_is_lock(code: Cow<'_, str>) -> bool { + let code = code.as_ref().to_ascii_lowercase(); + if matches!(code.as_str(), "sqlite_busy" | "sqlite_locked") { + return true; + } + code.parse::() + .ok() + .is_some_and(|code| matches!(code & 0xff, 5 | 6)) +} + pub fn sqlite_error_detail_is_corruption(detail: &str) -> bool { let detail = detail.to_ascii_lowercase(); detail.contains("database disk image is malformed") diff --git a/codex-rs/state/src/runtime/threads.rs b/codex-rs/state/src/runtime/threads.rs index 86200c59caa6..739f62258492 100644 --- a/codex-rs/state/src/runtime/threads.rs +++ b/codex-rs/state/src/runtime/threads.rs @@ -1118,6 +1118,8 @@ ON CONFLICT(id) DO UPDATE SET if thread_ids.is_empty() { return Ok(0); } + self.ensure_strict_thread_delete_available()?; + let logs_pool = self.logs_pool()?; let thread_id_strings = thread_ids .iter() @@ -1126,7 +1128,7 @@ ON CONFLICT(id) DO UPDATE SET for (thread_id, thread_id_string) in thread_ids.iter().zip(&thread_id_strings) { sqlx::query("DELETE FROM logs WHERE thread_id = ?") .bind(thread_id_string) - .execute(self.logs_pool.as_ref()) + .execute(logs_pool) .await?; self.thread_queue.delete_thread_queue(*thread_id).await?; self.memories.delete_thread_memory(*thread_id).await?; @@ -1959,7 +1961,12 @@ mod tests { .await?; seed_thread_cleanup_state(&runtime, thread_id, child_thread_id).await?; - runtime.logs_pool.close().await; + runtime + .logs_pool + .as_ref() + .expect("logs db should be available") + .close() + .await; runtime .delete_thread(thread_id) .await @@ -1996,7 +2003,12 @@ mod tests { .await?; sqlx::query("INSERT INTO logs (ts, ts_nanos, level, target, feedback_log_body, thread_id) VALUES (1, 0, 'INFO', 'test', 'feedback log', ?)") .bind(thread_id.to_string()) - .execute(runtime.logs_pool.as_ref()) + .execute( + runtime + .logs_pool + .as_deref() + .expect("logs db should be available"), + ) .await?; Ok(()) } From 8f1d18cbb78b5f29fac83fcb621db6b42f0b503f Mon Sep 17 00:00:00 2001 From: mcp-devbox Date: Tue, 25 Aug 2026 23:08:22 +0000 Subject: [PATCH 2/7] ci: validate state candidate on public runner --- .github/workflows/validate-state-35555.yml | 37 ++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/validate-state-35555.yml diff --git a/.github/workflows/validate-state-35555.yml b/.github/workflows/validate-state-35555.yml new file mode 100644 index 000000000000..fc219f1d7475 --- /dev/null +++ b/.github/workflows/validate-state-35555.yml @@ -0,0 +1,37 @@ +name: validate-state-candidate + +on: + push: + branches: + - "ci/35555-v3-public-runner" + +permissions: + contents: read + +jobs: + codex-state: + runs-on: ubuntu-24.04 + timeout-minutes: 45 + defaults: + run: + working-directory: codex-rs + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: 94a4bdb297a051d2f42daa8ec2a0f90ce1a9ff02 + persist-credentials: false + - uses: ./.github/actions/setup-ci + - uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0 + with: + components: clippy + - name: Install Linux build dependencies + shell: bash + run: | + sudo apt-get update -y + sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends build-essential pkg-config libcap-dev + - name: Clippy codex-state + run: cargo clippy -p codex-state --tests -- -D warnings + - name: Test codex-state serially + env: + RUST_MIN_STACK: "8388608" + run: cargo test -p codex-state -- --test-threads=1 From e6ee9944a875e1d2e5abb269955701d6a442ae5c Mon Sep 17 00:00:00 2001 From: mcp-devbox Date: Wed, 26 Aug 2026 11:06:45 +0000 Subject: [PATCH 3/7] ci: validate final state candidate --- .github/workflows/validate-state-35555.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/validate-state-35555.yml b/.github/workflows/validate-state-35555.yml index fc219f1d7475..a18c26be728e 100644 --- a/.github/workflows/validate-state-35555.yml +++ b/.github/workflows/validate-state-35555.yml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - ref: 94a4bdb297a051d2f42daa8ec2a0f90ce1a9ff02 + ref: 0bc3a2ac47372392370e240a223adc32c888ff30 persist-credentials: false - uses: ./.github/actions/setup-ci - uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0 @@ -29,6 +29,8 @@ jobs: run: | sudo apt-get update -y sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends build-essential pkg-config libcap-dev + - name: Check codex-app-server library + run: cargo check -p codex-app-server --lib - name: Clippy codex-state run: cargo clippy -p codex-state --tests -- -D warnings - name: Test codex-state serially From 75a7fc2387f7357c31b373beb49f079b2ce3f057 Mon Sep 17 00:00:00 2001 From: mcp-devbox Date: Wed, 26 Aug 2026 20:01:13 +0000 Subject: [PATCH 4/7] ci: revalidate state candidate on current main --- .github/workflows/validate-state-35555.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validate-state-35555.yml b/.github/workflows/validate-state-35555.yml index a18c26be728e..e36f799c9daf 100644 --- a/.github/workflows/validate-state-35555.yml +++ b/.github/workflows/validate-state-35555.yml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - ref: 0bc3a2ac47372392370e240a223adc32c888ff30 + ref: 711f352d8be57e62f0ab66ab273de4e31359995c persist-credentials: false - uses: ./.github/actions/setup-ci - uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0 From 4fbdc3165f48f1470efce7dd72b291f446a97808 Mon Sep 17 00:00:00 2001 From: mcp-devbox Date: Wed, 26 Aug 2026 20:10:27 +0000 Subject: [PATCH 5/7] ci: cover fix fmt and degraded stores --- .github/workflows/validate-state-35555.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/validate-state-35555.yml b/.github/workflows/validate-state-35555.yml index e36f799c9daf..ccbec2ed632c 100644 --- a/.github/workflows/validate-state-35555.yml +++ b/.github/workflows/validate-state-35555.yml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - ref: 711f352d8be57e62f0ab66ab273de4e31359995c + ref: 680038025ccec05808d11c37fa1a8de41a2ca8a0 persist-credentials: false - uses: ./.github/actions/setup-ci - uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0 @@ -29,6 +29,12 @@ jobs: run: | sudo apt-get update -y sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends build-essential pkg-config libcap-dev + - name: Fix codex-state + run: cargo clippy --fix --tests --allow-dirty -p codex-state + - name: Format Rust + run: cargo fmt -- --config imports_granularity=Item + - name: Verify fix and format are clean + run: git diff --exit-code - name: Check codex-app-server library run: cargo check -p codex-app-server --lib - name: Clippy codex-state From a7f005aa7894bff87f76c4c1e46a92910d7a2fac Mon Sep 17 00:00:00 2001 From: mcp-devbox Date: Wed, 26 Aug 2026 20:13:42 +0000 Subject: [PATCH 6/7] ci: validate v5 on latest upstream main --- .github/workflows/validate-state-35555.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validate-state-35555.yml b/.github/workflows/validate-state-35555.yml index ccbec2ed632c..8b426e18bf48 100644 --- a/.github/workflows/validate-state-35555.yml +++ b/.github/workflows/validate-state-35555.yml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - ref: 680038025ccec05808d11c37fa1a8de41a2ca8a0 + ref: e301966988da1d924e3318e1b7dd99e4d0b85e2b persist-credentials: false - uses: ./.github/actions/setup-ci - uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0 From c134e3620d423d55909285b0ffafd55be34564b0 Mon Sep 17 00:00:00 2001 From: mcp-devbox Date: Wed, 26 Aug 2026 20:15:57 +0000 Subject: [PATCH 7/7] ci: validate bounded lock wait --- .github/workflows/validate-state-35555.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validate-state-35555.yml b/.github/workflows/validate-state-35555.yml index 8b426e18bf48..f3848bb39f79 100644 --- a/.github/workflows/validate-state-35555.yml +++ b/.github/workflows/validate-state-35555.yml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - ref: e301966988da1d924e3318e1b7dd99e4d0b85e2b + ref: 61d04d21d842c27638d67464735e0ef9b2308007 persist-credentials: false - uses: ./.github/actions/setup-ci - uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0