codex: keep config lock independent of runtime lock

This commit is contained in:
Ahmed Ibrahim
2026-06-01 13:13:17 -07:00
parent 6b8fdbdf70
commit 38f6147d47
2 changed files with 9 additions and 83 deletions
+7 -72
View File
@@ -8,7 +8,6 @@ use codex_features::FeatureToml;
use codex_features::FeaturesToml;
use codex_features::MultiAgentV2ConfigToml;
use codex_protocol::ThreadId;
use codex_protocol::protocol::MultiAgentVersion;
use crate::config::Config;
use crate::config_lock::ConfigLockReplayOptions;
@@ -21,7 +20,6 @@ use super::SessionConfiguration;
pub(crate) async fn validate_config_lock_if_configured(
session_configuration: &SessionConfiguration,
multi_agent_version: Option<MultiAgentVersion>,
) -> anyhow::Result<()> {
if session_configuration.session_source.is_non_root_agent() {
return Ok(());
@@ -33,8 +31,7 @@ pub(crate) async fn validate_config_lock_if_configured(
else {
return Ok(());
};
let actual = session_configuration
.to_config_lockfile_toml_with_multi_agent_version(multi_agent_version)?;
let actual = session_configuration.to_config_lockfile_toml()?;
let config = session_configuration.original_config_do_not_use.as_ref();
let options = ConfigLockReplayOptions {
allow_codex_version_mismatch: config.config_lock_allow_codex_version_mismatch,
@@ -47,15 +44,13 @@ pub(crate) async fn validate_config_lock_if_configured(
pub(crate) async fn export_config_lock_if_configured(
session_configuration: &SessionConfiguration,
conversation_id: ThreadId,
multi_agent_version: Option<MultiAgentVersion>,
) -> anyhow::Result<()> {
let config = session_configuration.original_config_do_not_use.as_ref();
let Some(export_dir) = config.config_lock_export_dir.as_ref() else {
return Ok(());
};
let lock = session_configuration
.to_config_lockfile_toml_with_multi_agent_version(multi_agent_version)?;
let lock = session_configuration.to_config_lockfile_toml()?;
let lock = toml::to_string_pretty(&lock).context("failed to serialize config lock")?;
let path = export_dir.join(format!("{conversation_id}.config.lock.toml"));
@@ -75,28 +70,15 @@ pub(crate) async fn export_config_lock_if_configured(
}
impl SessionConfiguration {
#[cfg(test)]
pub(crate) fn to_config_lockfile_toml(&self) -> anyhow::Result<ConfigLockfileToml> {
self.to_config_lockfile_toml_with_multi_agent_version(
self.original_config_do_not_use
.multi_agent_version_from_features(),
)
}
fn to_config_lockfile_toml_with_multi_agent_version(
&self,
multi_agent_version: Option<MultiAgentVersion>,
) -> anyhow::Result<ConfigLockfileToml> {
Ok(config_lockfile(session_configuration_to_lock_config_toml(
self,
multi_agent_version,
)?))
}
}
fn session_configuration_to_lock_config_toml(
sc: &SessionConfiguration,
multi_agent_version: Option<MultiAgentVersion>,
) -> anyhow::Result<ConfigToml> {
let config = sc.original_config_do_not_use.as_ref();
// Start from the resolved layer stack, then patch in values that are only
@@ -112,7 +94,7 @@ fn session_configuration_to_lock_config_toml(
save_session_resolved_fields(sc, &mut lock_config);
}
save_config_resolved_fields(config, multi_agent_version, &mut lock_config)?;
save_config_resolved_fields(config, &mut lock_config)?;
drop_lockfile_inputs(&mut lock_config);
Ok(lock_config)
@@ -143,7 +125,6 @@ fn save_session_resolved_fields(sc: &SessionConfiguration, lock_config: &mut Con
/// Codex actually ran with, not only the user-authored TOML inputs.
fn save_config_resolved_fields(
config: &Config,
multi_agent_version: Option<MultiAgentVersion>,
lock_config: &mut ConfigToml,
) -> anyhow::Result<()> {
lock_config.web_search = Some(config.web_search_mode.value());
@@ -163,16 +144,10 @@ fn save_config_resolved_fields(
let features = lock_config
.features
.get_or_insert_with(FeaturesToml::default);
let mut materialized_features = config.features.get().clone();
materialized_features.set_enabled(Feature::Collab, multi_agent_version.is_some());
materialized_features.set_enabled(
Feature::MultiAgentV2,
multi_agent_version == Some(MultiAgentVersion::V2),
);
features.materialize_resolved_enabled(&materialized_features);
features.materialize_resolved_enabled(config.features.get());
let mut multi_agent_v2: MultiAgentV2ConfigToml =
resolved_config_to_toml(&config.multi_agent_v2, "features.multi_agent_v2")?;
multi_agent_v2.enabled = Some(multi_agent_version == Some(MultiAgentVersion::V2));
multi_agent_v2.enabled = Some(config.features.enabled(Feature::MultiAgentV2));
features.multi_agent_v2 = Some(FeatureToml::Config(multi_agent_v2));
features.apps_mcp_path_override = Some(FeatureToml::Config(AppsMcpPathOverrideConfigToml {
enabled: Some(config.features.enabled(Feature::AppsMcpPathOverride)),
@@ -186,10 +161,10 @@ fn save_config_resolved_fields(
let agents = lock_config.agents.get_or_insert_with(Default::default);
// Multi-agent v2 owns thread fanout through its feature config. Preserve
// the legacy agents.max_threads setting only when v2 is disabled.
agents.max_threads = if multi_agent_version == Some(MultiAgentVersion::V2) {
agents.max_threads = if config.features.enabled(Feature::MultiAgentV2) {
None
} else {
config.effective_agent_max_threads(multi_agent_version)?
config.agent_max_threads
};
agents.max_depth = Some(config.agent_max_depth);
agents.job_max_runtime_seconds = config.agent_job_max_runtime_seconds;
@@ -298,46 +273,6 @@ mod tests {
assert_eq!(lockfile.version, crate::config_lock::CONFIG_LOCK_VERSION);
}
#[tokio::test]
async fn lock_projects_multi_agent_version_over_current_feature_flags() {
let mut sc = crate::session::tests::make_session_configuration_for_tests().await;
let mut config = (*sc.original_config_do_not_use).clone();
config
.features
.enable(Feature::MultiAgentV2)
.expect("test config should allow feature update");
sc.original_config_do_not_use = Arc::new(config);
for (multi_agent_version, collab, multi_agent_v2) in [
(None, false, false),
(Some(MultiAgentVersion::V1), true, false),
(Some(MultiAgentVersion::V2), true, true),
] {
let lockfile = sc
.to_config_lockfile_toml_with_multi_agent_version(multi_agent_version)
.expect("lock should serialize");
let lock = &lockfile.config;
let entries = lock
.features
.as_ref()
.expect("lock should materialize features")
.entries();
assert_eq!(entries.get(Feature::Collab.key()), Some(&collab));
assert_eq!(
entries.get(Feature::MultiAgentV2.key()),
Some(&multi_agent_v2)
);
assert_eq!(
lock.agents
.as_ref()
.expect("lock should materialize agent config")
.max_threads
.is_none(),
multi_agent_version == Some(MultiAgentVersion::V2)
);
}
}
#[tokio::test]
async fn lock_skips_session_values_when_model_catalog_fields_are_not_saved() {
let mut sc = crate::session::tests::make_session_configuration_for_tests().await;
+2 -11
View File
@@ -888,17 +888,8 @@ impl Session {
))
.await;
session_configuration.thread_name = thread_name.clone();
validate_config_lock_if_configured(
&session_configuration,
initial_multi_agent_version,
)
.await?;
export_config_lock_if_configured(
&session_configuration,
thread_id,
initial_multi_agent_version,
)
.await?;
validate_config_lock_if_configured(&session_configuration).await?;
export_config_lock_if_configured(&session_configuration, thread_id).await?;
let state = SessionState::new(session_configuration.clone());
let managed_network_requirements_configured = config
.config_layer_stack