Stop migrating Cursor sandbox settings (#39325)

## What changed

- Ignore `.cursor/sandbox.json` during external agent config migration.
- Continue importing supported settings from `.cursor/cli-config.json`.

## Testing

- Update app-server migration coverage to verify that environment variables from `cli-config.json` are imported.

GitOrigin-RevId: 6094f1cb6f698392d58056f826da21c1f1389a1b
This commit is contained in:
charlesgong-openai
2026-08-19 01:00:58 +00:00
committed by copyberry
parent b537d5a097
commit 67ed4e717a
5 changed files with 10 additions and 156 deletions

View File

@@ -338,7 +338,10 @@ async fn external_agent_config_migration_source_drives_detect_and_import() -> Re
let codex_home = TempDir::new()?;
let source_home = secondary_external_agent_home(codex_home.path());
std::fs::create_dir_all(&source_home)?;
std::fs::write(source_home.join("sandbox.json"), r#"{"type":"read_only"}"#)?;
std::fs::write(
source_home.join("cli-config.json"),
r#"{"env":{"SOURCE":"secondary"}}"#,
)?;
let home_dir = codex_home.path().display().to_string();
let mut mcp = TestAppServer::builder()
.with_codex_home(codex_home.path())
@@ -387,7 +390,7 @@ async fn external_agent_config_migration_source_drives_detect_and_import() -> Re
assert_eq!(completed.item_type_results[0].failures, Vec::new());
assert!(
std::fs::read_to_string(codex_home.path().join("config.toml"))?
.contains("sandbox_mode = \"read-only\"")
.contains("SOURCE = \"secondary\"")
);
Ok(())

View File

@@ -98,12 +98,11 @@ impl ExternalAgentSource {
pub(super) fn effective_settings(
self,
source_config_dir: &Path,
source_settings: &Path,
) -> io::Result<Option<JsonValue>> {
match self {
Self::Cla => ClaSource::effective_settings(source_settings),
Self::Cur => CurSource::effective_settings(source_config_dir, source_settings),
Self::Cur => CurSource::effective_settings(source_settings),
}
}

View File

@@ -485,8 +485,7 @@ impl ExternalAgentConfigService {
scope: &MigrationScope,
) -> io::Result<Option<JsonValue>> {
let source_settings = self.source_settings(scope);
self.source
.effective_settings(self.source_config_dir(scope).as_path(), &source_settings)
self.source.effective_settings(&source_settings)
}
pub(crate) fn build_mcp_config(

View File

@@ -7,7 +7,6 @@ use crate::build_mcp_config_from_json_file;
use crate::hook_migration_event_names_cur;
use crate::import_hooks_cur;
use crate::import_subagents_with_rewrite_profile;
use crate::invalid_data_error;
use serde_json::Value as JsonValue;
use std::fs;
use std::io;
@@ -22,97 +21,16 @@ impl CurSource {
pub const LEGACY_RULES_FILE: &'static str = ".cursorrules";
pub const HOME_CONFIG_FILE: &'static str = "cli-config.json";
pub const PROJECT_CONFIG_FILE: &'static str = "cli.json";
pub const SANDBOX_CONFIG_FILE: &'static str = "sandbox.json";
pub const HOOKS_CONFIG_FILE: &'static str = "hooks.json";
pub const SANDBOX_SETTINGS_KEY: &'static str = "__cursorSandbox";
pub const REWRITE_PROFILE: RewriteProfile = RewriteProfile::new(Self::LEGACY_RULES_FILE, &[])
.with_case_sensitive_term_variants(&["Cursor"]);
pub fn effective_settings(
source_dir: &Path,
source_settings: &Path,
) -> io::Result<Option<JsonValue>> {
let mut effective = read_json_file(source_settings)?;
let sandbox_settings = read_json_file(&source_dir.join(Self::SANDBOX_CONFIG_FILE))?;
if let Some(sandbox_settings) = sandbox_settings {
let effective =
effective.get_or_insert_with(|| JsonValue::Object(serde_json::Map::new()));
let Some(effective) = effective.as_object_mut() else {
return Err(invalid_data_error(
"external agent settings root must be an object",
));
};
effective.insert(Self::SANDBOX_SETTINGS_KEY.to_string(), sandbox_settings);
}
Ok(effective)
pub fn effective_settings(source_settings: &Path) -> io::Result<Option<JsonValue>> {
read_json_file(source_settings)
}
pub fn build_config(settings: &JsonValue) -> io::Result<TomlValue> {
build_config(settings, Self::append_config)
}
pub fn append_config(
root: &mut toml::map::Map<String, TomlValue>,
settings: &serde_json::Map<String, JsonValue>,
) {
let Some(sandbox) = settings
.get(Self::SANDBOX_SETTINGS_KEY)
.and_then(JsonValue::as_object)
else {
return;
};
let sandbox_mode = match sandbox.get("type").and_then(JsonValue::as_str) {
Some("workspace_readwrite") => Some("workspace-write"),
Some("read_only") => Some("read-only"),
_ => None,
};
if let Some(sandbox_mode) = sandbox_mode {
root.insert(
"sandbox_mode".to_string(),
TomlValue::String(sandbox_mode.to_string()),
);
}
if sandbox_mode != Some("workspace-write") {
return;
}
let mut workspace_write = toml::map::Map::new();
if let Some(paths) = sandbox
.get("additionalReadwritePaths")
.and_then(JsonValue::as_array)
{
let paths = paths
.iter()
.filter_map(JsonValue::as_str)
.filter(|path| Path::new(path).is_absolute())
.map(|path| TomlValue::String(path.to_string()))
.collect::<Vec<_>>();
if !paths.is_empty() {
workspace_write.insert("writable_roots".to_string(), TomlValue::Array(paths));
}
}
if sandbox.get("disableTmpWrite").and_then(JsonValue::as_bool) == Some(true) {
workspace_write.insert("exclude_slash_tmp".to_string(), TomlValue::Boolean(true));
workspace_write.insert(
"exclude_tmpdir_env_var".to_string(),
TomlValue::Boolean(true),
);
}
if sandbox
.get("networkPolicy")
.and_then(JsonValue::as_object)
.and_then(|network| network.get("default"))
.and_then(JsonValue::as_str)
== Some("allow")
{
workspace_write.insert("network_access".to_string(), TomlValue::Boolean(true));
}
if !workspace_write.is_empty() {
root.insert(
"sandbox_workspace_write".to_string(),
TomlValue::Table(workspace_write),
);
}
build_config(settings, |_, _| {})
}
pub fn build_mcp_config(source_dir: &Path) -> io::Result<TomlValue> {

View File

@@ -7,71 +7,6 @@ use crate::model::PluginsMigration;
use pretty_assertions::assert_eq;
use std::collections::HashSet;
use tempfile::TempDir;
use toml::Value as TomlValue;
#[test]
fn effective_settings_merge_sandbox_configuration() {
let root = TempDir::new().expect("tempdir");
let source_dir = root.path().join(CurSource::CONFIG_DIR);
let source_settings = source_dir.join(CurSource::HOME_CONFIG_FILE);
fs::create_dir_all(&source_dir).expect("source directory");
fs::write(&source_settings, r#"{"env":{"FOO":"bar"}}"#).expect("source settings");
fs::write(
source_dir.join(CurSource::SANDBOX_CONFIG_FILE),
r#"{"type":"read_only"}"#,
)
.expect("sandbox settings");
assert_eq!(
CurSource::effective_settings(&source_dir, &source_settings).expect("effective settings"),
Some(serde_json::json!({
"env": {"FOO": "bar"},
(CurSource::SANDBOX_SETTINGS_KEY): {"type": "read_only"}
}))
);
}
#[test]
fn append_config_maps_workspace_permissions() {
let root = TempDir::new().expect("tempdir");
let writable_root = root.path().join("generated");
let settings = serde_json::json!({
(CurSource::SANDBOX_SETTINGS_KEY): {
"type": "workspace_readwrite",
"additionalReadwritePaths": [writable_root.display().to_string(), "relative/path"],
"disableTmpWrite": true,
"networkPolicy": {"default": "allow"}
}
});
let mut config = toml::map::Map::new();
CurSource::append_config(&mut config, settings.as_object().expect("settings object"));
let mut workspace_write = toml::map::Map::new();
workspace_write.insert(
"writable_roots".to_string(),
TomlValue::Array(vec![TomlValue::String(
writable_root.to_string_lossy().into_owned(),
)]),
);
workspace_write.insert("exclude_slash_tmp".to_string(), TomlValue::Boolean(true));
workspace_write.insert(
"exclude_tmpdir_env_var".to_string(),
TomlValue::Boolean(true),
);
workspace_write.insert("network_access".to_string(), TomlValue::Boolean(true));
let mut expected = toml::map::Map::new();
expected.insert(
"sandbox_mode".to_string(),
TomlValue::String("workspace-write".to_string()),
);
expected.insert(
"sandbox_workspace_write".to_string(),
TomlValue::Table(workspace_write),
);
assert_eq!(TomlValue::Table(config), TomlValue::Table(expected));
}
#[test]
fn cached_marketplace_plugins_require_manifest_and_cache_entries() {