Revert state DB injection and agent graph store (#21481)

## Why

Reverts #20689 to restore the previous optional state DB plumbing. The
conflict resolution keeps the newer installation ID and session/thread
identity changes that landed after #20689, while removing the mandatory
state DB and agent graph store dependency from ThreadManager
construction.

## What changed

- Restored `Option<StateDbHandle>` through app-server, MCP server,
prompt debug, and test entry points.
- Removed the `codex-core` dependency on `codex-agent-graph-store` and
reverted descendant lookup back to the existing state DB path when
available.
- Kept newer `installation_id` forwarding by passing it beside the
optional DB handle.
- Kept local thread-name updates working when the optional state DB
handle is absent.

## Validation

- `git diff --check`
- `cargo test -p codex-thread-store`
- `cargo test -p codex-state -p codex-rollout -p
codex-app-server-protocol`
- Attempted `env CARGO_INCREMENTAL=0 cargo test -p codex-core -p
codex-app-server -p codex-app-server-client -p codex-mcp-server -p
codex-thread-manager-sample -p codex-tui`; blocked locally by a rustc
ICE while compiling `v8 v146.4.0` with `rustc 1.93.0 (254b59607
2026-01-19)` on `aarch64-apple-darwin`.
This commit is contained in:
pakrym-oai
2026-05-06 22:48:29 -07:00
committed by GitHub
Unverified
parent 5bc33fe31f
commit a8488fec5e
54 changed files with 781 additions and 834 deletions
+12 -5
View File
@@ -132,6 +132,7 @@ use codex_terminal_detection::user_agent;
use codex_thread_store::CreateThreadParams;
use codex_thread_store::LiveThread;
use codex_thread_store::LiveThreadInitGuard;
use codex_thread_store::LocalThreadStore;
use codex_thread_store::ResumeThreadParams;
use codex_thread_store::ThreadEventPersistenceMode;
use codex_thread_store::ThreadPersistenceMetadata;
@@ -352,6 +353,7 @@ use codex_protocol::protocol::TokenUsage;
use codex_protocol::protocol::TokenUsageInfo;
use codex_protocol::protocol::WarningEvent;
use codex_protocol::user_input::UserInput;
use codex_tools::ToolEnvironmentMode;
use codex_tools::ToolsConfig;
use codex_tools::ToolsConfigParams;
use codex_utils_absolute_path::AbsolutePathBuf;
@@ -409,7 +411,6 @@ pub(crate) struct CodexSpawnArgs {
pub(crate) parent_trace: Option<W3cTraceContext>,
pub(crate) environment_selections: ResolvedTurnEnvironments,
pub(crate) analytics_events_client: Option<AnalyticsEventsClient>,
pub(crate) state_db: Option<state_db::StateDbHandle>,
pub(crate) thread_store: Arc<dyn ThreadStore>,
}
@@ -469,7 +470,6 @@ impl Codex {
parent_trace: _,
environment_selections,
analytics_events_client,
state_db,
thread_store,
} = args;
let (tx_sub, rx_sub) = async_channel::bounded(SUBMISSION_CHANNEL_CAPACITY);
@@ -558,7 +558,15 @@ impl Codex {
};
match thread_id {
Some(thread_id) => {
let state_db_ctx = state_db.clone();
let state_db_ctx = if config.ephemeral {
None
} else if let Some(local_store) =
thread_store.as_any().downcast_ref::<LocalThreadStore>()
{
local_store.state_db().await
} else {
None
};
state_db::get_dynamic_tools(state_db_ctx.as_deref(), thread_id, "codex_spawn")
.await
}
@@ -646,7 +654,6 @@ impl Codex {
agent_control,
environment_manager,
analytics_events_client,
state_db,
thread_store,
parent_rollout_thread_trace,
)
@@ -1308,7 +1315,7 @@ impl Session {
self.services.user_shell.as_ref().clone(),
self.services.shell_snapshot_tx.clone(),
self.services.session_telemetry.clone(),
self.state_db(),
self.services.state_db.clone(),
);
}
+18 -4
View File
@@ -368,7 +368,6 @@ impl Session {
agent_control: AgentControl,
environment_manager: Arc<EnvironmentManager>,
analytics_events_client: Option<AnalyticsEventsClient>,
state_db: Option<state_db::StateDbHandle>,
thread_store: Arc<dyn ThreadStore>,
parent_rollout_thread_trace: ThreadTraceContext,
) -> anyhow::Result<Arc<Self>> {
@@ -468,7 +467,22 @@ impl Session {
otel.name = "session_init.thread_persistence",
session_init.ephemeral = config.ephemeral,
));
let state_db_ctx = if config.ephemeral { None } else { state_db };
let state_db_fut = async {
if config.ephemeral {
None
} else if let Some(local_store) =
thread_store.as_any().downcast_ref::<LocalThreadStore>()
{
local_store.state_db().await
} else {
None
}
}
.instrument(info_span!(
"session_init.state_db",
otel.name = "session_init.state_db",
session_init.ephemeral = config.ephemeral,
));
let auth_manager_clone = Arc::clone(&auth_manager);
let config_for_mcp = Arc::clone(&config);
@@ -492,8 +506,8 @@ impl Session {
));
// Join all independent futures.
let (thread_persistence_result, (auth, mcp_servers, auth_statuses)) =
tokio::join!(thread_persistence_fut, auth_and_mcp_fut);
let (thread_persistence_result, state_db_ctx, (auth, mcp_servers, auth_statuses)) =
tokio::join!(thread_persistence_fut, state_db_fut, auth_and_mcp_fut);
let mut live_thread_init =
LiveThreadInitGuard::new(thread_persistence_result.map_err(|e| {
+86 -73
View File
@@ -915,7 +915,7 @@ async fn danger_full_access_turns_do_not_expose_managed_network_proxy() -> anyho
&permission_profile_for_sandbox_policy(&SandboxPolicy::DangerFullAccess),
)?;
let (session, _codex_home) = make_session_with_config(move |config| {
let session = make_session_with_config(move |config| {
let cwd = config.cwd.clone();
config
.permissions
@@ -981,7 +981,7 @@ async fn danger_full_access_tool_attempts_do_not_enforce_managed_network() -> an
&permission_profile_for_sandbox_policy(&SandboxPolicy::DangerFullAccess),
)?;
let (session, _codex_home) = make_session_with_config(move |config| {
let session = make_session_with_config(move |config| {
let cwd = config.cwd.clone();
config
.permissions
@@ -1056,7 +1056,7 @@ async fn workspace_write_turns_continue_to_expose_managed_network_proxy() -> any
&permission_profile_for_sandbox_policy(&sandbox_policy),
)?;
let (session, _codex_home) = make_session_with_config(move |config| {
let session = make_session_with_config(move |config| {
let cwd = config.cwd.clone();
config
.permissions
@@ -1083,7 +1083,7 @@ async fn user_shell_commands_do_not_inherit_managed_network_proxy() -> anyhow::R
&permission_profile_for_sandbox_policy(&sandbox_policy),
)?;
let (session, rx, _codex_home) = make_session_with_config_and_rx(move |config| {
let (session, rx) = make_session_with_config_and_rx(move |config| {
let cwd = config.cwd.clone();
config
.permissions
@@ -1213,7 +1213,7 @@ async fn reload_user_config_layer_updates_effective_apps_config() {
#[tokio::test]
async fn reload_user_config_layer_refreshes_hooks() -> anyhow::Result<()> {
let (session, _codex_home) = make_session_with_config(|config| {
let session = make_session_with_config(|config| {
config
.features
.enable(Feature::CodexHooks)
@@ -3052,13 +3052,15 @@ pub(crate) async fn make_session_configuration_for_tests() -> SessionConfigurati
fn turn_environments_for_tests(
environment: &Arc<codex_exec_server::Environment>,
cwd: &codex_utils_absolute_path::AbsolutePathBuf,
) -> Vec<TurnEnvironment> {
vec![TurnEnvironment {
environment_id: codex_exec_server::LOCAL_ENVIRONMENT_ID.to_string(),
environment: Arc::clone(environment),
cwd: cwd.clone(),
shell: None,
}]
) -> crate::environment_selection::ResolvedTurnEnvironments {
crate::environment_selection::ResolvedTurnEnvironments {
turn_environments: vec![TurnEnvironment {
environment_id: codex_exec_server::LOCAL_ENVIRONMENT_ID.to_string(),
environment: Arc::clone(environment),
cwd: cwd.clone(),
shell: None,
}],
}
}
#[tokio::test]
@@ -3598,15 +3600,9 @@ async fn session_new_fails_when_zsh_fork_enabled_without_zsh_path() {
AgentControl::default(),
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
/*analytics_events_client*/ None,
/*state_db*/ None,
Arc::new(codex_thread_store::LocalThreadStore::new(
codex_thread_store::LocalThreadStoreConfig::from_config(config.as_ref()),
codex_state::StateRuntime::init(
config.sqlite_home.clone(),
config.model_provider_id.clone(),
)
.await
.expect("state db should initialize"),
/*state_db*/ None,
)),
codex_rollout_trace::ThreadTraceContext::disabled(),
)
@@ -3755,12 +3751,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
live_thread: None,
thread_store: Arc::new(codex_thread_store::LocalThreadStore::new(
codex_thread_store::LocalThreadStoreConfig::from_config(config.as_ref()),
codex_state::StateRuntime::init(
config.sqlite_home.clone(),
config.model_provider_id.clone(),
)
.await
.expect("state db should initialize"),
/*state_db*/ None,
)),
model_client: ModelClient::new(
Some(auth_manager.clone()),
@@ -3807,7 +3798,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
model_info,
&models_manager,
/*network*/ None,
crate::environment_selection::ResolvedTurnEnvironments { turn_environments },
turn_environments,
session_configuration.cwd.clone(),
"turn_id".to_string(),
skills_outcome,
@@ -3841,18 +3832,14 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
async fn make_session_with_config(
mutator: impl FnOnce(&mut Config),
) -> anyhow::Result<(Arc<Session>, tempfile::TempDir)> {
let (session, _rx_event, codex_home) = make_session_with_config_and_rx(mutator).await?;
Ok((session, codex_home))
) -> anyhow::Result<Arc<Session>> {
let (session, _rx_event) = make_session_with_config_and_rx(mutator).await?;
Ok(session)
}
async fn make_session_with_config_and_rx(
mutator: impl FnOnce(&mut Config),
) -> anyhow::Result<(
Arc<Session>,
async_channel::Receiver<Event>,
tempfile::TempDir,
)> {
) -> anyhow::Result<(Arc<Session>, async_channel::Receiver<Event>)> {
let codex_home = tempfile::tempdir().expect("create temp dir");
let mut config = build_test_config(codex_home.path()).await;
mutator(&mut config);
@@ -3939,21 +3926,15 @@ async fn make_session_with_config_and_rx(
AgentControl::default(),
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
/*analytics_events_client*/ None,
/*state_db*/ None,
Arc::new(codex_thread_store::LocalThreadStore::new(
codex_thread_store::LocalThreadStoreConfig::from_config(config.as_ref()),
codex_state::StateRuntime::init(
config.sqlite_home.clone(),
config.model_provider_id.clone(),
)
.await
.expect("state db should initialize"),
/*state_db*/ None,
)),
codex_rollout_trace::ThreadTraceContext::disabled(),
)
.await?;
Ok((session, rx_event, codex_home))
Ok((session, rx_event))
}
async fn make_session_with_history_source_and_agent_control_and_rx(
@@ -4047,15 +4028,16 @@ async fn make_session_with_history_source_and_agent_control_and_rx(
agent_control,
Arc::new(codex_exec_server::EnvironmentManager::default_for_tests()),
/*analytics_events_client*/ None,
/*state_db*/ None,
Arc::new(codex_thread_store::LocalThreadStore::new(
codex_thread_store::LocalThreadStoreConfig::from_config(config.as_ref()),
codex_state::StateRuntime::init(
config.sqlite_home.clone(),
config.model_provider_id.clone(),
)
.await
.expect("state db should initialize"),
Some(
codex_state::StateRuntime::init(
config.sqlite_home.clone(),
config.model_provider_id.clone(),
)
.await
.expect("state db should initialize"),
),
)),
codex_rollout_trace::ThreadTraceContext::disabled(),
)
@@ -4784,7 +4766,7 @@ async fn default_turn_honors_empty_stored_thread_environments() {
let turn_context = session.new_default_turn().await;
assert!(turn_context.environments.primary_environment().is_none());
assert!(turn_context.environments.primary().is_none());
assert!(turn_context.environments.turn_environments.is_empty());
assert_eq!(turn_context.cwd, session_cwd);
assert_eq!(turn_context.config.cwd, session_cwd);
@@ -4846,7 +4828,7 @@ async fn empty_turn_environments_clear_primary_environment() {
.await
.expect("turn should start");
assert!(turn_context.environments.primary_environment().is_none());
assert!(turn_context.environments.primary().is_none());
assert!(turn_context.environments.turn_environments.is_empty());
assert_eq!(turn_context.cwd, session.get_config().await.cwd);
assert_eq!(turn_context.config.cwd, session.get_config().await.cwd);
@@ -5303,13 +5285,47 @@ async fn make_session_and_context_with_auth_and_config_and_rx<F>(
Arc<TurnContext>,
async_channel::Receiver<Event>,
)
where
F: FnOnce(&mut Config),
{
let codex_home = tempfile::tempdir().expect("create temp dir");
make_session_and_context_with_auth_config_home_and_rx(
auth,
dynamic_tools,
codex_home.path(),
configure_config,
)
.await
}
async fn make_session_and_context_with_auth_config_home_and_rx<F>(
auth: CodexAuth,
dynamic_tools: Vec<DynamicToolSpec>,
codex_home: &Path,
configure_config: F,
) -> (
Arc<Session>,
Arc<TurnContext>,
async_channel::Receiver<Event>,
)
where
F: FnOnce(&mut Config),
{
let (tx_event, rx_event) = async_channel::unbounded();
let codex_home = tempfile::tempdir().expect("create temp dir").keep();
let mut config = build_test_config(codex_home.as_path()).await;
let mut config = build_test_config(codex_home).await;
configure_config(&mut config);
let state_db = if config.features.enabled(Feature::Goals) {
Some(
codex_state::StateRuntime::init(
config.sqlite_home.clone(),
config.model_provider_id.clone(),
)
.await
.expect("goal tests should initialize sqlite state db"),
)
} else {
None
};
let config = Arc::new(config);
let thread_id = ThreadId::default();
let auth_manager = AuthManager::from_auth_for_testing(auth);
@@ -5396,12 +5412,6 @@ where
.expect("create environment"),
);
let state_db = codex_state::StateRuntime::init(
config.sqlite_home.clone(),
config.model_provider_id.clone(),
)
.await
.expect("state db should initialize");
let skills_watcher = Arc::new(SkillsWatcher::noop());
let services = SessionServices {
mcp_connection_manager: Arc::new(RwLock::new(McpConnectionManager::new_uninitialized(
@@ -5442,7 +5452,7 @@ where
agent_control,
network_proxy: None,
network_approval: Arc::clone(&network_approval),
state_db: Some(state_db.clone()),
state_db: state_db.clone(),
live_thread: None,
thread_store: Arc::new(codex_thread_store::LocalThreadStore::new(
codex_thread_store::LocalThreadStoreConfig::from_config(config.as_ref()),
@@ -5493,7 +5503,7 @@ where
model_info,
&models_manager,
/*network*/ None,
crate::environment_selection::ResolvedTurnEnvironments { turn_environments },
turn_environments,
session_configuration.cwd.clone(),
"turn_id".to_string(),
skills_outcome,
@@ -5544,10 +5554,13 @@ async fn make_goal_session_and_context_with_rx() -> (
Arc<Session>,
Arc<TurnContext>,
async_channel::Receiver<Event>,
tempfile::TempDir,
) {
let (session, turn_context, rx) = make_session_and_context_with_auth_and_config_and_rx(
let codex_home = tempfile::tempdir().expect("create temp dir");
let (session, turn_context, rx) = make_session_and_context_with_auth_config_home_and_rx(
CodexAuth::from_api_key("Test API Key"),
Vec::new(),
codex_home.path(),
|config| {
config
.features
@@ -5557,14 +5570,14 @@ async fn make_goal_session_and_context_with_rx() -> (
)
.await;
upsert_goal_test_thread(session.as_ref()).await;
(session, turn_context, rx)
(session, turn_context, rx, codex_home)
}
async fn upsert_goal_test_thread(session: &Session) {
let config = session.get_config().await;
let state_db = goal_test_state_db(session)
.await
.expect("goal test state db should initialize");
let state_db = session
.state_db()
.expect("goal test session should have a state db");
let mut builder = codex_state::ThreadMetadataBuilder::new(
session.conversation_id,
config
@@ -7429,7 +7442,7 @@ async fn abort_empty_active_turn_preserves_pending_input() {
#[tokio::test]
async fn interrupt_accounts_active_goal_before_pausing() -> anyhow::Result<()> {
let (sess, tc, _rx) = make_goal_session_and_context_with_rx().await;
let (sess, tc, _rx, _codex_home) = make_goal_session_and_context_with_rx().await;
sess.set_thread_goal(
tc.as_ref(),
SetGoalRequest {
@@ -7693,7 +7706,7 @@ async fn goal_test_state_db(sess: &Session) -> anyhow::Result<crate::StateDbHand
#[tokio::test]
async fn budget_limited_accounting_steers_active_turn_without_aborting() -> anyhow::Result<()> {
let (sess, tc, rx) = make_goal_session_and_context_with_rx().await;
let (sess, tc, rx, _codex_home) = make_goal_session_and_context_with_rx().await;
sess.set_thread_goal(
tc.as_ref(),
SetGoalRequest {
@@ -7793,7 +7806,7 @@ async fn budget_limited_accounting_steers_active_turn_without_aborting() -> anyh
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn external_goal_mutation_accounts_active_turn_before_status_change() -> anyhow::Result<()> {
let (sess, tc, _rx) = make_goal_session_and_context_with_rx().await;
let (sess, tc, _rx, _codex_home) = make_goal_session_and_context_with_rx().await;
sess.set_thread_goal(
tc.as_ref(),
SetGoalRequest {
@@ -7860,7 +7873,7 @@ async fn external_goal_mutation_accounts_active_turn_before_status_change() -> a
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn external_active_goal_set_marks_current_turn_for_accounting() -> anyhow::Result<()> {
let (sess, tc, _rx) = make_goal_session_and_context_with_rx().await;
let (sess, tc, _rx, _codex_home) = make_goal_session_and_context_with_rx().await;
sess.spawn_task(
Arc::clone(&tc),
Vec::new(),
@@ -8527,7 +8540,7 @@ async fn sample_rollout(
#[tokio::test]
async fn create_goal_tool_rejects_existing_goal() {
let (session, turn_context, _rx) = make_goal_session_and_context_with_rx().await;
let (session, turn_context, _rx, _codex_home) = make_goal_session_and_context_with_rx().await;
let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::new()));
let handler = CreateGoalHandler;
@@ -8589,7 +8602,7 @@ async fn create_goal_tool_rejects_existing_goal() {
#[tokio::test]
async fn update_goal_tool_rejects_pausing_goal() {
let (session, turn_context, _rx) = make_goal_session_and_context_with_rx().await;
let (session, turn_context, _rx, _codex_home) = make_goal_session_and_context_with_rx().await;
let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::new()));
let create_handler = CreateGoalHandler;
let update_handler = UpdateGoalHandler;
@@ -8650,7 +8663,7 @@ async fn update_goal_tool_rejects_pausing_goal() {
#[tokio::test]
async fn update_goal_tool_marks_goal_complete() {
let (session, turn_context, _rx) = make_goal_session_and_context_with_rx().await;
let (session, turn_context, _rx, _codex_home) = make_goal_session_and_context_with_rx().await;
let tracker = Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::new()));
let create_handler = CreateGoalHandler;
let update_handler = UpdateGoalHandler;
@@ -731,12 +731,7 @@ async fn guardian_subagent_does_not_inherit_parent_exec_policy_rules() {
let skills_watcher = Arc::new(SkillsWatcher::noop());
let thread_store = Arc::new(codex_thread_store::LocalThreadStore::new(
codex_thread_store::LocalThreadStoreConfig::from_config(&config),
codex_state::StateRuntime::init(
config.sqlite_home.clone(),
config.model_provider_id.clone(),
)
.await
.expect("state db should initialize"),
/*state_db*/ None,
));
let CodexSpawnOk { codex, .. } = Codex::spawn(CodexSpawnArgs {
@@ -767,7 +762,6 @@ async fn guardian_subagent_does_not_inherit_parent_exec_policy_rules() {
turn_environments: Vec::new(),
},
analytics_events_client: None,
state_db: None,
thread_store,
})
.await
@@ -11,7 +11,6 @@ use codex_protocol::protocol::TurnEnvironmentSelection;
use codex_sandboxing::compatibility_sandbox_policy_for_permission_profile;
use codex_sandboxing::policy_transforms::effective_file_system_sandbox_policy;
use codex_sandboxing::policy_transforms::effective_network_sandbox_policy;
use codex_tools::ToolEnvironmentMode;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;