Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions conformance/vectors/claude_isolation_sources/api-auth-darwin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "api-auth-darwin",
"op": "claude_isolation_sources",
"input": {
"host": {
"platform": "darwin",
"home": "/Users/testuser",
"claude_config_dir_env": null,
"claude_securestorage_config_dir_env": null,
"claude_code_custom_oauth_url_env": null,
"claude_code_oauth_token_env": null
},
"api_auth": true
},
"expected": {
"account_path": null,
"credentials_path": null,
"keychain_service": null
}
}
71 changes: 71 additions & 0 deletions conformance/vectors/plan/codex-bypass-approvals-and-sandbox.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{
"name": "codex-bypass-approvals-and-sandbox",
"op": "plan",
"input": {
"provider": "codex",
"spec": {
"prompt": "hi",
"model": "gpt-5.5",
"agent": false,
"isolated": true,
"timeout": 180,
"max_attempts": 5,
"api_auth": false,
"schema": null,
"apple": null,
"claude": null,
"codex": {
"bypass_approvals_and_sandbox": true,
"developer_instructions": null,
"enable_hooks": false,
"enable_mcp": false,
"sandbox": null,
"service_tier": "fast"
},
"gemini": null,
"openai_endpoint": null
},
"host": {
"platform": "darwin"
}
},
"expected": {
"kind": "exec",
"argv": [
"codex",
"exec",
"--ephemeral",
"--dangerously-bypass-approvals-and-sandbox",
"--skip-git-repo-check",
"--color",
"never",
"--model",
"gpt-5.5",
"-c",
"service_tier=fast",
"--ignore-user-config",
"-c",
"features.hooks=false",
"-c",
"features.mcp_servers=false",
"-o",
"${file:result}"
],
"stdin": "hi",
"files": [
{
"id": "result",
"suffix": ".json",
"content": null
}
],
"stdout_to_file": false,
"read_result_from": "file:result",
"env": {},
"env_unset": [
"OPENAI_API_KEY",
"CODEX_API_KEY"
],
"needs_claude_isolation": false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"apple": null,
"claude": null,
"codex": {
"bypass_approvals_and_sandbox": false,
"developer_instructions": "ab",
"enable_hooks": false,
"enable_mcp": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"apple": null,
"claude": null,
"codex": {
"bypass_approvals_and_sandbox": false,
"developer_instructions": "true",
"enable_hooks": false,
"enable_mcp": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"apple": null,
"claude": null,
"codex": {
"bypass_approvals_and_sandbox": false,
"developer_instructions": "Be terse.\nCite sources.",
"enable_hooks": false,
"enable_mcp": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"apple": null,
"claude": null,
"codex": {
"bypass_approvals_and_sandbox": false,
"developer_instructions": "café ☕ 你好",
"enable_hooks": false,
"enable_mcp": false,
Expand Down
1 change: 1 addition & 0 deletions conformance/vectors/plan/codex-enable-hooks.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"apple": null,
"claude": null,
"codex": {
"bypass_approvals_and_sandbox": false,
"developer_instructions": null,
"enable_hooks": true,
"enable_mcp": false,
Expand Down
1 change: 1 addition & 0 deletions conformance/vectors/plan/codex-enable-mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"apple": null,
"claude": null,
"codex": {
"bypass_approvals_and_sandbox": false,
"developer_instructions": null,
"enable_hooks": false,
"enable_mcp": true,
Expand Down
1 change: 1 addition & 0 deletions conformance/vectors/plan/codex-sandbox-override.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"apple": null,
"claude": null,
"codex": {
"bypass_approvals_and_sandbox": false,
"developer_instructions": null,
"enable_hooks": false,
"enable_mcp": false,
Expand Down
1 change: 1 addition & 0 deletions conformance/vectors/plan/codex-service-tier-none.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"apple": null,
"claude": null,
"codex": {
"bypass_approvals_and_sandbox": false,
"developer_instructions": null,
"enable_hooks": false,
"enable_mcp": false,
Expand Down
9 changes: 5 additions & 4 deletions go/coreops.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ type authProbes struct {
}

type isolationSources struct {
AccountPath string `json:"account_path"`
AccountPath *string `json:"account_path"`
CredentialsPath *string `json:"credentials_path"`
KeychainService *string `json:"keychain_service"`
}
Expand Down Expand Up @@ -198,7 +198,7 @@ func coreAuthProbes(provider Provider) (authProbes, error) {
}{Provider: provider, Host: map[string]string{"platform": platform(), "home": home()}})
}

func coreIsolationSources() (isolationSources, error) {
func coreIsolationSources(apiAuth bool) (isolationSources, error) {
host := map[string]any{
"platform": platform(),
"home": home(),
Expand All @@ -220,8 +220,9 @@ func coreIsolationSources() (isolationSources, error) {
host["claude_code_oauth_token_env"] = token
}
return coreInto[isolationSources]("claude_isolation_sources", struct {
Host map[string]any `json:"host"`
}{Host: host})
Host map[string]any `json:"host"`
APIAuth bool `json:"api_auth"`
}{Host: host, APIAuth: apiAuth})
}

func coreIsolationSeed(accountJSON, credentialsJSON *string) (isolationSeed, error) {
Expand Down
2 changes: 1 addition & 1 deletion go/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func runExecPlan(ctx context.Context, plan execPlan, spec RunSpec) (output strin

env := plan.Env
if plan.NeedsClaudeIsolation {
dir, seedEnv, cleanup, e := seedClaudeIsolation()
dir, seedEnv, cleanup, e := seedClaudeIsolation(spec.APIAuth)
if e != nil {
return "", 0, "", false, e
}
Expand Down
9 changes: 6 additions & 3 deletions go/isolate.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ import (
"strings"
)

func seedClaudeIsolation() (string, map[string]string, func(), error) {
sources, err := coreIsolationSources()
func seedClaudeIsolation(apiAuth bool) (string, map[string]string, func(), error) {
sources, err := coreIsolationSources(apiAuth)
if err != nil {
return "", nil, nil, err
}
accountJSON := readFileOpt(sources.AccountPath)
var accountJSON *string
if sources.AccountPath != nil {
accountJSON = readFileOpt(*sources.AccountPath)
}
var credentialsJSON *string
if sources.CredentialsPath != nil {
credentialsJSON = readFileOpt(*sources.CredentialsPath)
Expand Down
25 changes: 15 additions & 10 deletions go/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ type CodexConfig struct {
// other value selects that tier.
ServiceTier *string
DeveloperInstructions string
// BypassApprovalsAndSandbox replaces --sandbox with
// --dangerously-bypass-approvals-and-sandbox for an externally sandboxed host.
BypassApprovalsAndSandbox bool
}

// GeminiConfig passes knobs through to the gemini and antigravity CLIs.
Expand Down Expand Up @@ -216,11 +219,12 @@ type coreClaude struct {
}

type coreCodex struct {
DeveloperInstructions *string `json:"developer_instructions"`
EnableHooks bool `json:"enable_hooks"`
EnableMCP bool `json:"enable_mcp"`
Sandbox *string `json:"sandbox"`
ServiceTier *string `json:"service_tier"`
BypassApprovalsAndSandbox bool `json:"bypass_approvals_and_sandbox"`
DeveloperInstructions *string `json:"developer_instructions"`
EnableHooks bool `json:"enable_hooks"`
EnableMCP bool `json:"enable_mcp"`
Sandbox *string `json:"sandbox"`
ServiceTier *string `json:"service_tier"`
}

type coreGemini struct {
Expand Down Expand Up @@ -339,11 +343,12 @@ func coreCodexOf(c *CodexConfig) *coreCodex {
tier = nil
}
return &coreCodex{
DeveloperInstructions: optString(c.DeveloperInstructions),
EnableHooks: c.EnableHooks,
EnableMCP: c.EnableMCP,
Sandbox: optString(c.Sandbox),
ServiceTier: tier,
BypassApprovalsAndSandbox: c.BypassApprovalsAndSandbox,
DeveloperInstructions: optString(c.DeveloperInstructions),
EnableHooks: c.EnableHooks,
EnableMCP: c.EnableMCP,
Sandbox: optString(c.Sandbox),
ServiceTier: tier,
}
}

Expand Down
15 changes: 15 additions & 0 deletions rust/conformance-gen/src/cases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ fn claude() -> ClaudeConfig {

fn codex() -> CodexConfig {
CodexConfig {
bypass_approvals_and_sandbox: false,
developer_instructions: None,
enable_hooks: false,
enable_mcp: false,
Expand Down Expand Up @@ -390,6 +391,13 @@ fn plan_cases() -> Vec<Case> {
..codex()
},
),
codex_cfg_case(
"codex-bypass-approvals-and-sandbox",
CodexConfig {
bypass_approvals_and_sandbox: true,
..codex()
},
),
codex_cfg_case(
"codex-enable-hooks",
CodexConfig {
Expand Down Expand Up @@ -1075,8 +1083,15 @@ fn iso_sources_case(name: &str, host: IsoHost) -> Case {
}
}

fn iso_api_case(name: &str, host: IsoHost) -> Case {
let mut case = iso_sources_case(name, host);
case.input["api_auth"] = json!(true);
case
}

fn iso_sources_cases() -> Vec<Case> {
vec![
iso_api_case("api-auth-darwin", ISO_DARWIN),
iso_sources_case("default-home-darwin", ISO_DARWIN),
iso_sources_case("default-home-linux", ISO_LINUX),
iso_sources_case(
Expand Down
13 changes: 11 additions & 2 deletions rust/spawnllm-core/src/isolate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ use crate::{OpError, OpResult, from_input, unimplemented};
#[derive(Debug, Deserialize)]
struct IsolationSourcesInput {
host: IsolationHost,
#[serde(default)]
api_auth: bool,
}

#[derive(Debug, Deserialize)]
Expand All @@ -29,7 +31,7 @@ struct IsolationHost {

#[derive(Debug, Serialize)]
struct IsolationSources {
account_path: String,
account_path: Option<String>,
credentials_path: Option<String>,
keychain_service: Option<String>,
}
Expand Down Expand Up @@ -113,6 +115,13 @@ fn config_dir_digest(config_dir_env: &str) -> String {
// Mirrors Claude Code 2.1.274's lb()/mI(): a defined CLAUDE_SECURESTORAGE_CONFIG_DIR
// overrides CLAUDE_CONFIG_DIR for the storage dir and the digest of the NFC value as set.
fn isolation_sources(input: IsolationSourcesInput) -> IsolationSources {
if input.api_auth {
return IsolationSources {
account_path: None,
credentials_path: None,
keychain_service: None,
};
}
let host = input.host;
let inherits_token = is_set(&host.claude_code_oauth_token_env);
let default_home = format!("{}/.claude", host.home);
Expand Down Expand Up @@ -146,7 +155,7 @@ fn isolation_sources(input: IsolationSourcesInput) -> IsolationSources {
format!("Claude Code{oauth_file_suffix}-credentials{digest}")
});
IsolationSources {
account_path,
account_path: Some(account_path),
credentials_path: (!inherits_token)
.then(|| format!("{credentials_home}/.credentials.json")),
keychain_service,
Expand Down
22 changes: 13 additions & 9 deletions rust/spawnllm-core/src/plan/codex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,24 @@ pub(super) fn plan(spec: &RunSpec) -> InvocationPlan {
.map_or((spec.model.as_str(), None), |(model, effort)| {
(model, (!effort.is_empty()).then_some(effort))
});
let mut argv = vec![
"codex".into(),
"exec".into(),
"--ephemeral".into(),
"--sandbox".into(),
config
.and_then(|config| config.sandbox.clone())
.unwrap_or_else(|| "read-only".into()),
let mut argv: Vec<String> = vec!["codex".into(), "exec".into(), "--ephemeral".into()];
if config.is_some_and(|config| config.bypass_approvals_and_sandbox) {
argv.push("--dangerously-bypass-approvals-and-sandbox".into());
} else {
argv.extend([
"--sandbox".into(),
config
.and_then(|config| config.sandbox.clone())
.unwrap_or_else(|| "read-only".into()),
]);
}
argv.extend([
"--skip-git-repo-check".into(),
"--color".into(),
"never".into(),
"--model".into(),
model.into(),
];
]);

if let Some(effort) = effort {
push_config(&mut argv, format!("model_reasoning_effort={effort}"));
Expand Down
1 change: 1 addition & 0 deletions rust/spawnllm-core/src/wire.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ pub struct ClaudeConfig {

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CodexConfig {
pub bypass_approvals_and_sandbox: bool,
pub developer_instructions: Option<String>,
pub enable_hooks: bool,
pub enable_mcp: bool,
Expand Down
Loading
Loading