diff --git a/codex-rs/app-server/src/lib.rs b/codex-rs/app-server/src/lib.rs index c1a164e47c..d37d034154 100644 --- a/codex-rs/app-server/src/lib.rs +++ b/codex-rs/app-server/src/lib.rs @@ -526,10 +526,7 @@ pub async fn run_main_with_transport_options( return Err(err); } - let message = config_warning_from_error( - "Invalid configuration; using defaults with SQLite disabled.", - &err, - ); + let message = config_warning_from_error("Invalid configuration; using defaults.", &err); config_warnings.push(message); let mut config = config_manager.load_default_config().await.map_err(|e| { std::io::Error::new( @@ -544,6 +541,14 @@ pub async fn run_main_with_transport_options( (config, false) } }; + if !config.features.enabled(codex_features::Feature::Sqlite) { + config_warnings.push(ConfigWarningNotification { + summary: "SQLite is disabled. Codex is running in degraded mode; SQLite-dependent features and operations are unavailable.".to_string(), + details: None, + path: None, + range: None, + }); + } let otel = codex_core::otel_init::build_provider( &config, diff --git a/codex-rs/app-server/tests/suite/v2/config_rpc.rs b/codex-rs/app-server/tests/suite/v2/config_rpc.rs index b1ef93d70f..01d89f969a 100644 --- a/codex-rs/app-server/tests/suite/v2/config_rpc.rs +++ b/codex-rs/app-server/tests/suite/v2/config_rpc.rs @@ -40,6 +40,7 @@ use tokio::time::timeout; // Bazel CI can spend tens of seconds starting app-server subprocesses or // processing config RPCs under load. const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(60); +const SQLITE_DISABLED_WARNING_SUMMARY: &str = "SQLite is disabled. Codex is running in degraded mode; SQLite-dependent features and operations are unavailable."; fn write_config(codex_home: &TempDir, contents: &str) -> Result<()> { Ok(std::fs::write( @@ -48,6 +49,39 @@ fn write_config(codex_home: &TempDir, contents: &str) -> Result<()> { )?) } +#[tokio::test] +async fn sqlite_disabled_emits_degraded_mode_warning() -> Result<()> { + let codex_home = TempDir::new()?; + write_config(&codex_home, "[features]\nsqlite = false\n")?; + let mut mcp = + TestAppServer::new_with_env(codex_home.path(), &[("CODEX_SQLITE_HOME", None)]).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + + let warning = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_matching_notification("SQLite disabled config warning", |message| { + message.method == "configWarning" + && message + .params + .as_ref() + .and_then(|params| params.get("summary")) + .and_then(serde_json::Value::as_str) + == Some(SQLITE_DISABLED_WARNING_SUMMARY) + }), + ) + .await??; + + assert_eq!( + warning + .params + .as_ref() + .and_then(|params| params.get("summary")) + .and_then(serde_json::Value::as_str), + Some(SQLITE_DISABLED_WARNING_SUMMARY) + ); + Ok(()) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn config_requirements_read_includes_allow_remote_control() -> Result<()> { let codex_home = TempDir::new()?;