Compare commits

...

5 Commits

Author SHA1 Message Date
kevin zhao
34809c9880 timezones 2025-10-30 18:17:52 -04:00
kevin zhao
325e35388c adding system date to env context 2025-10-30 18:00:32 -04:00
Bernard Niset
ff6d4cec6b fix: Update seatbelt policy for java on macOS (#3987)
# Summary

This PR is related to the Issue #3978 and contains a fix to the seatbelt
profile for macOS that allows to run java/jdk tooling from the sandbox.
I have found that the included change is the minimum change to make it
run on my machine.

There is a unit test added by codex when making this fix. I wonder if it
is useful since you need java installed on the target machine for it to
be relevant. I can remove it it is better.

Fixes #3978
2025-10-30 14:25:04 -07:00
Celia Chen
6ef658a9f9 [Hygiene] Remove include_view_image_tool config (#5976)
There's still some debate about whether we want to expose
`tools.view_image` or `feature.view_image` so those are left unchanged
for now, but this old `include_view_image_tool` config is good-to-go.
Also updated the doc to reflect that `view_image` tool is now by default
true.
2025-10-30 13:23:24 -07:00
Brad M. Harris
8b8be343a7 Documentation improvement: add missing period (#3754)
Pull request template, minimal:

---

### **What?**

Minor change (low-hanging fruit).

### **Why?**

To improve code quality or documentation with minimal risk and effort.

### **How?**

Edited directly via VSCode Editor.

---

**Checklist (pre-PR):**

* [x] I have read the CLA Document and hereby sign the CLA.
* [x] I reviewed the “Contributing” markdown file for this project.

*This template meets standard external (non-OpenAI) PR requirements and
signals compliance for maintainers.*

Co-authored-by: Eric Traut <etraut@openai.com>
2025-10-30 13:01:33 -07:00
15 changed files with 122 additions and 46 deletions

View File

@@ -1 +1 @@
The changelog can be found on the [releases page](https://github.com/openai/codex/releases)
The changelog can be found on the [releases page](https://github.com/openai/codex/releases).

View File

@@ -1777,7 +1777,6 @@ async fn derive_config_from_params(
developer_instructions,
compact_prompt,
include_apply_patch_tool,
include_view_image_tool: None,
show_raw_agent_reasoning: None,
tools_web_search_request: None,
experimental_sandbox_command_assessment: None,

View File

@@ -116,6 +116,7 @@ use crate::user_instructions::DeveloperInstructions;
use crate::user_instructions::UserInstructions;
use crate::user_notification::UserNotification;
use crate::util::backoff;
use chrono::Local;
use codex_async_utils::OrCancelExt;
use codex_otel::otel_event_manager::OtelEventManager;
use codex_protocol::config_types::ReasoningEffort as ReasoningEffortConfig;
@@ -267,6 +268,7 @@ pub(crate) struct TurnContext {
/// the model as well as sandbox policies are resolved against this path
/// instead of `std::env::current_dir()`.
pub(crate) cwd: PathBuf,
pub(crate) local_date_with_timezone: Option<String>,
pub(crate) developer_instructions: Option<String>,
pub(crate) base_instructions: Option<String>,
pub(crate) compact_prompt: Option<String>,
@@ -423,6 +425,7 @@ impl Session {
sub_id,
client,
cwd: session_configuration.cwd.clone(),
local_date_with_timezone: Some(Local::now().format("%Y-%m-%d %:z").to_string()),
developer_instructions: session_configuration.developer_instructions.clone(),
base_instructions: session_configuration.base_instructions.clone(),
compact_prompt: session_configuration.compact_prompt.clone(),
@@ -1007,6 +1010,7 @@ impl Session {
}
items.push(ResponseItem::from(EnvironmentContext::new(
Some(turn_context.cwd.clone()),
turn_context.local_date_with_timezone.clone(),
Some(turn_context.approval_policy),
Some(turn_context.sandbox_policy.clone()),
Some(self.user_shell().clone()),
@@ -1692,6 +1696,7 @@ async fn spawn_review_thread(
sandbox_policy: parent_turn_context.sandbox_policy.clone(),
shell_environment_policy: parent_turn_context.shell_environment_policy.clone(),
cwd: parent_turn_context.cwd.clone(),
local_date_with_timezone: parent_turn_context.local_date_with_timezone.clone(),
final_output_json_schema: None,
codex_linux_sandbox_exe: parent_turn_context.codex_linux_sandbox_exe.clone(),
tool_call_gate: Arc::new(ReadinessFlag::new()),

View File

@@ -250,9 +250,6 @@ pub struct Config {
/// https://github.com/modelcontextprotocol/rust-sdk
pub use_experimental_use_rmcp_client: bool,
/// Include the `view_image` tool that lets the agent attach a local image path to context.
pub include_view_image_tool: bool,
/// Centralized feature flags; source of truth for feature gating.
pub features: Features,
@@ -841,7 +838,6 @@ pub struct ConfigOverrides {
pub developer_instructions: Option<String>,
pub compact_prompt: Option<String>,
pub include_apply_patch_tool: Option<bool>,
pub include_view_image_tool: Option<bool>,
pub show_raw_agent_reasoning: Option<bool>,
pub tools_web_search_request: Option<bool>,
pub experimental_sandbox_command_assessment: Option<bool>,
@@ -873,7 +869,6 @@ impl Config {
developer_instructions,
compact_prompt,
include_apply_patch_tool: include_apply_patch_tool_override,
include_view_image_tool: include_view_image_tool_override,
show_raw_agent_reasoning,
tools_web_search_request: override_tools_web_search_request,
experimental_sandbox_command_assessment: sandbox_command_assessment_override,
@@ -900,7 +895,6 @@ impl Config {
let feature_overrides = FeatureOverrides {
include_apply_patch_tool: include_apply_patch_tool_override,
include_view_image_tool: include_view_image_tool_override,
web_search_request: override_tools_web_search_request,
experimental_sandbox_command_assessment: sandbox_command_assessment_override,
};
@@ -998,7 +992,6 @@ impl Config {
let history = cfg.history.unwrap_or_default();
let include_apply_patch_tool_flag = features.enabled(Feature::ApplyPatchFreeform);
let include_view_image_tool_flag = features.enabled(Feature::ViewImageTool);
let tools_web_search_request = features.enabled(Feature::WebSearchRequest);
let use_experimental_streamable_shell_tool = features.enabled(Feature::StreamableShell);
let use_experimental_unified_exec_tool = features.enabled(Feature::UnifiedExec);
@@ -1160,7 +1153,6 @@ impl Config {
use_experimental_streamable_shell_tool,
use_experimental_unified_exec_tool,
use_experimental_use_rmcp_client,
include_view_image_tool: include_view_image_tool_flag,
features,
active_profile: active_profile_name,
active_project,
@@ -1595,7 +1587,7 @@ trust_level = "trusted"
profiles.insert(
"work".to_string(),
ConfigProfile {
include_view_image_tool: Some(false),
tools_view_image: Some(false),
..Default::default()
},
);
@@ -1612,7 +1604,6 @@ trust_level = "trusted"
)?;
assert!(!config.features.enabled(Feature::ViewImageTool));
assert!(!config.include_view_image_tool);
Ok(())
}
@@ -2908,7 +2899,6 @@ model_verbosity = "high"
use_experimental_streamable_shell_tool: false,
use_experimental_unified_exec_tool: false,
use_experimental_use_rmcp_client: false,
include_view_image_tool: true,
features: Features::with_defaults(),
active_profile: Some("o3".to_string()),
active_project: ProjectConfig { trust_level: None },
@@ -2981,7 +2971,6 @@ model_verbosity = "high"
use_experimental_streamable_shell_tool: false,
use_experimental_unified_exec_tool: false,
use_experimental_use_rmcp_client: false,
include_view_image_tool: true,
features: Features::with_defaults(),
active_profile: Some("gpt3".to_string()),
active_project: ProjectConfig { trust_level: None },
@@ -3069,7 +3058,6 @@ model_verbosity = "high"
use_experimental_streamable_shell_tool: false,
use_experimental_unified_exec_tool: false,
use_experimental_use_rmcp_client: false,
include_view_image_tool: true,
features: Features::with_defaults(),
active_profile: Some("zdr".to_string()),
active_project: ProjectConfig { trust_level: None },
@@ -3143,7 +3131,6 @@ model_verbosity = "high"
use_experimental_streamable_shell_tool: false,
use_experimental_unified_exec_tool: false,
use_experimental_use_rmcp_client: false,
include_view_image_tool: true,
features: Features::with_defaults(),
active_profile: Some("gpt5".to_string()),
active_project: ProjectConfig { trust_level: None },

View File

@@ -24,7 +24,6 @@ pub struct ConfigProfile {
pub experimental_instructions_file: Option<PathBuf>,
pub experimental_compact_prompt_file: Option<PathBuf>,
pub include_apply_patch_tool: Option<bool>,
pub include_view_image_tool: Option<bool>,
pub experimental_use_unified_exec_tool: Option<bool>,
pub experimental_use_exec_command_tool: Option<bool>,
pub experimental_use_rmcp_client: Option<bool>,

View File

@@ -24,6 +24,7 @@ pub enum NetworkAccess {
#[serde(rename = "environment_context", rename_all = "snake_case")]
pub(crate) struct EnvironmentContext {
pub cwd: Option<PathBuf>,
pub local_date: Option<String>,
pub approval_policy: Option<AskForApproval>,
pub sandbox_mode: Option<SandboxMode>,
pub network_access: Option<NetworkAccess>,
@@ -34,12 +35,14 @@ pub(crate) struct EnvironmentContext {
impl EnvironmentContext {
pub fn new(
cwd: Option<PathBuf>,
local_date: Option<String>,
approval_policy: Option<AskForApproval>,
sandbox_policy: Option<SandboxPolicy>,
shell: Option<Shell>,
) -> Self {
Self {
cwd,
local_date,
approval_policy,
sandbox_mode: match sandbox_policy {
Some(SandboxPolicy::DangerFullAccess) => Some(SandboxMode::DangerFullAccess),
@@ -79,6 +82,7 @@ impl EnvironmentContext {
pub fn equals_except_shell(&self, other: &EnvironmentContext) -> bool {
let EnvironmentContext {
cwd,
local_date,
approval_policy,
sandbox_mode,
network_access,
@@ -88,6 +92,7 @@ impl EnvironmentContext {
} = other;
self.cwd == *cwd
&& self.local_date == *local_date
&& self.approval_policy == *approval_policy
&& self.sandbox_mode == *sandbox_mode
&& self.network_access == *network_access
@@ -100,6 +105,11 @@ impl EnvironmentContext {
} else {
None
};
let local_date = if before.local_date_with_timezone != after.local_date_with_timezone {
after.local_date_with_timezone.clone()
} else {
None
};
let approval_policy = if before.approval_policy != after.approval_policy {
Some(after.approval_policy)
} else {
@@ -110,7 +120,7 @@ impl EnvironmentContext {
} else {
None
};
EnvironmentContext::new(cwd, approval_policy, sandbox_policy, None)
EnvironmentContext::new(cwd, local_date, approval_policy, sandbox_policy, None)
}
}
@@ -118,6 +128,7 @@ impl From<&TurnContext> for EnvironmentContext {
fn from(turn_context: &TurnContext) -> Self {
Self::new(
Some(turn_context.cwd.clone()),
turn_context.local_date_with_timezone.clone(),
Some(turn_context.approval_policy),
Some(turn_context.sandbox_policy.clone()),
// Shell is not configurable from turn to turn
@@ -134,6 +145,7 @@ impl EnvironmentContext {
/// ```xml
/// <environment_context>
/// <cwd>...</cwd>
/// <local_date>...</local_date>
/// <approval_policy>...</approval_policy>
/// <sandbox_mode>...</sandbox_mode>
/// <writable_roots>...</writable_roots>
@@ -146,6 +158,9 @@ impl EnvironmentContext {
if let Some(cwd) = self.cwd {
lines.push(format!(" <cwd>{}</cwd>", cwd.to_string_lossy()));
}
if let Some(local_date) = self.local_date {
lines.push(format!(" <local_date>{local_date}</local_date>"));
}
if let Some(approval_policy) = self.approval_policy {
lines.push(format!(
" <approval_policy>{approval_policy}</approval_policy>"
@@ -212,6 +227,7 @@ mod tests {
fn serialize_workspace_write_environment_context() {
let context = EnvironmentContext::new(
Some(PathBuf::from("/repo")),
Some("2025-01-01 +00:00".to_string()),
Some(AskForApproval::OnRequest),
Some(workspace_write_policy(vec!["/repo", "/tmp"], false)),
None,
@@ -219,6 +235,7 @@ mod tests {
let expected = r#"<environment_context>
<cwd>/repo</cwd>
<local_date>2025-01-01 +00:00</local_date>
<approval_policy>on-request</approval_policy>
<sandbox_mode>workspace-write</sandbox_mode>
<network_access>restricted</network_access>
@@ -235,12 +252,14 @@ mod tests {
fn serialize_read_only_environment_context() {
let context = EnvironmentContext::new(
None,
Some("2025-01-01 +00:00".to_string()),
Some(AskForApproval::Never),
Some(SandboxPolicy::ReadOnly),
None,
);
let expected = r#"<environment_context>
<local_date>2025-01-01 +00:00</local_date>
<approval_policy>never</approval_policy>
<sandbox_mode>read-only</sandbox_mode>
<network_access>restricted</network_access>
@@ -253,12 +272,14 @@ mod tests {
fn serialize_full_access_environment_context() {
let context = EnvironmentContext::new(
None,
Some("2025-01-01 +00:00".to_string()),
Some(AskForApproval::OnFailure),
Some(SandboxPolicy::DangerFullAccess),
None,
);
let expected = r#"<environment_context>
<local_date>2025-01-01 +00:00</local_date>
<approval_policy>on-failure</approval_policy>
<sandbox_mode>danger-full-access</sandbox_mode>
<network_access>enabled</network_access>
@@ -272,12 +293,14 @@ mod tests {
// Approval policy
let context1 = EnvironmentContext::new(
Some(PathBuf::from("/repo")),
Some("2025-01-01 +00:00".to_string()),
Some(AskForApproval::OnRequest),
Some(workspace_write_policy(vec!["/repo"], false)),
None,
);
let context2 = EnvironmentContext::new(
Some(PathBuf::from("/repo")),
Some("2025-01-01 +00:00".to_string()),
Some(AskForApproval::Never),
Some(workspace_write_policy(vec!["/repo"], true)),
None,
@@ -289,12 +312,14 @@ mod tests {
fn equals_except_shell_compares_sandbox_policy() {
let context1 = EnvironmentContext::new(
Some(PathBuf::from("/repo")),
Some("2025-01-01 +00:00".to_string()),
Some(AskForApproval::OnRequest),
Some(SandboxPolicy::new_read_only_policy()),
None,
);
let context2 = EnvironmentContext::new(
Some(PathBuf::from("/repo")),
Some("2025-01-01 +00:00".to_string()),
Some(AskForApproval::OnRequest),
Some(SandboxPolicy::new_workspace_write_policy()),
None,
@@ -307,12 +332,14 @@ mod tests {
fn equals_except_shell_compares_workspace_write_policy() {
let context1 = EnvironmentContext::new(
Some(PathBuf::from("/repo")),
Some("2025-01-01 +00:00".to_string()),
Some(AskForApproval::OnRequest),
Some(workspace_write_policy(vec!["/repo", "/tmp", "/var"], false)),
None,
);
let context2 = EnvironmentContext::new(
Some(PathBuf::from("/repo")),
Some("2025-01-01 +00:00".to_string()),
Some(AskForApproval::OnRequest),
Some(workspace_write_policy(vec!["/repo", "/tmp"], true)),
None,
@@ -325,6 +352,7 @@ mod tests {
fn equals_except_shell_ignores_shell() {
let context1 = EnvironmentContext::new(
Some(PathBuf::from("/repo")),
Some("2025-01-01 +00:00".to_string()),
Some(AskForApproval::OnRequest),
Some(workspace_write_policy(vec!["/repo"], false)),
Some(Shell::Bash(BashShell {
@@ -334,6 +362,7 @@ mod tests {
);
let context2 = EnvironmentContext::new(
Some(PathBuf::from("/repo")),
Some("2025-01-01 +00:00".to_string()),
Some(AskForApproval::OnRequest),
Some(workspace_write_policy(vec!["/repo"], false)),
Some(Shell::Zsh(ZshShell {

View File

@@ -82,7 +82,6 @@ pub struct Features {
#[derive(Debug, Clone, Default)]
pub struct FeatureOverrides {
pub include_apply_patch_tool: Option<bool>,
pub include_view_image_tool: Option<bool>,
pub web_search_request: Option<bool>,
pub experimental_sandbox_command_assessment: Option<bool>,
}
@@ -91,7 +90,6 @@ impl FeatureOverrides {
fn apply(self, features: &mut Features) {
LegacyFeatureToggles {
include_apply_patch_tool: self.include_apply_patch_tool,
include_view_image_tool: self.include_view_image_tool,
tools_web_search: self.web_search_request,
..Default::default()
}
@@ -193,7 +191,6 @@ impl Features {
let profile_legacy = LegacyFeatureToggles {
include_apply_patch_tool: config_profile.include_apply_patch_tool,
include_view_image_tool: config_profile.include_view_image_tool,
experimental_sandbox_command_assessment: config_profile
.experimental_sandbox_command_assessment,
experimental_use_freeform_apply_patch: config_profile

View File

@@ -33,10 +33,6 @@ const ALIASES: &[Alias] = &[
legacy_key: "include_apply_patch_tool",
feature: Feature::ApplyPatchFreeform,
},
Alias {
legacy_key: "include_view_image_tool",
feature: Feature::ViewImageTool,
},
Alias {
legacy_key: "web_search",
feature: Feature::WebSearchRequest,
@@ -56,7 +52,6 @@ pub(crate) fn feature_for_key(key: &str) -> Option<Feature> {
#[derive(Debug, Default)]
pub struct LegacyFeatureToggles {
pub include_apply_patch_tool: Option<bool>,
pub include_view_image_tool: Option<bool>,
pub experimental_sandbox_command_assessment: Option<bool>,
pub experimental_use_freeform_apply_patch: Option<bool>,
pub experimental_use_exec_command_tool: Option<bool>,
@@ -110,12 +105,6 @@ impl LegacyFeatureToggles {
self.tools_web_search,
"tools.web_search",
);
set_if_some(
features,
Feature::ViewImageTool,
self.include_view_image_tool,
"include_view_image_tool",
);
set_if_some(
features,
Feature::ViewImageTool,

View File

@@ -71,6 +71,10 @@
(sysctl-name-prefix "net.routetable.")
)
; Allow Java to set CPU type grade when required
(allow sysctl-write
(sysctl-name "kern.grade_cputype"))
; IOKit
(allow iokit-open
(iokit-registry-entry-class "RootDomainUserClient")

View File

@@ -1,5 +1,6 @@
#![allow(clippy::unwrap_used)]
use chrono::Local;
use codex_core::CodexAuth;
use codex_core::ConversationManager;
use codex_core::ModelProviderInfo;
@@ -40,9 +41,11 @@ fn text_user_input(text: String) -> serde_json::Value {
}
fn default_env_context_str(cwd: &str, shell: &Shell) -> String {
let local_date = Local::now().format("%Y-%m-%d %:z").to_string();
format!(
r#"<environment_context>
<cwd>{}</cwd>
<local_date>{local_date}</local_date>
<approval_policy>on-request</approval_policy>
<sandbox_mode>read-only</sandbox_mode>
<network_access>restricted</network_access>
@@ -344,19 +347,23 @@ async fn prefixes_context_and_instructions_once_and_consistently_across_requests
let shell = default_user_shell().await;
let expected_env_text = format!(
r#"<environment_context>
let expected_env_text = {
let local_date = Local::now().format("%Y-%m-%d %:z").to_string();
format!(
r#"<environment_context>
<cwd>{}</cwd>
<local_date>{local_date}</local_date>
<approval_policy>on-request</approval_policy>
<sandbox_mode>read-only</sandbox_mode>
<network_access>restricted</network_access>
{}</environment_context>"#,
cwd.path().to_string_lossy(),
match shell.name() {
Some(name) => format!(" <shell>{name}</shell>\n"),
None => String::new(),
}
);
cwd.path().to_string_lossy(),
match shell.name() {
Some(name) => format!(" <shell>{name}</shell>\n"),
None => String::new(),
}
)
};
let expected_ui_text =
"<user_instructions>\n\nbe consistent and helpful\n\n</user_instructions>";

View File

@@ -203,6 +203,69 @@ async fn python_getpwuid_works_under_seatbelt() {
assert!(status.success(), "python exited with {status:?}");
}
#[tokio::test]
async fn java_home_finds_runtime_under_seatbelt() {
if std::env::var(CODEX_SANDBOX_ENV_VAR) == Ok("seatbelt".to_string()) {
eprintln!("{CODEX_SANDBOX_ENV_VAR} is set to 'seatbelt', skipping test.");
return;
}
let java_home_path = Path::new("/usr/libexec/java_home");
if !java_home_path.exists() {
eprintln!("/usr/libexec/java_home is not present, skipping test.");
return;
}
let baseline_output = tokio::process::Command::new(java_home_path)
.env_remove("JAVA_HOME")
.output()
.await
.expect("should be able to invoke java_home outside seatbelt");
if !baseline_output.status.success() {
eprintln!(
"java_home exited with {:?} outside seatbelt, skipping test",
baseline_output.status
);
return;
}
let policy = SandboxPolicy::ReadOnly;
let command_cwd = std::env::current_dir().expect("getcwd");
let sandbox_cwd = command_cwd.clone();
let mut env: HashMap<String, String> = std::env::vars().collect();
env.remove("JAVA_HOME");
env.remove(CODEX_SANDBOX_ENV_VAR);
let child = spawn_command_under_seatbelt(
vec![java_home_path.to_string_lossy().to_string()],
command_cwd,
&policy,
sandbox_cwd.as_path(),
StdioPolicy::RedirectForShellTool,
env,
)
.await
.expect("should be able to spawn java_home under seatbelt");
let output = child
.wait_with_output()
.await
.expect("should be able to wait for java_home child");
assert!(
output.status.success(),
"java_home under seatbelt exited with {:?}, stderr: {}",
output.status,
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
!stdout.trim().is_empty(),
"java_home stdout unexpectedly empty under seatbelt"
);
}
#[expect(clippy::expect_used)]
fn create_test_scenario(tmp: &TempDir) -> TestScenario {
let repo_parent = tmp.path().to_path_buf();

View File

@@ -177,7 +177,6 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option<PathBuf>) -> any
developer_instructions: None,
compact_prompt: None,
include_apply_patch_tool: None,
include_view_image_tool: None,
show_raw_agent_reasoning: oss.then_some(true),
tools_web_search_request: None,
experimental_sandbox_command_assessment: None,

View File

@@ -167,7 +167,6 @@ impl CodexToolCallParam {
developer_instructions,
compact_prompt,
include_apply_patch_tool: None,
include_view_image_tool: None,
show_raw_agent_reasoning: None,
tools_web_search_request: None,
experimental_sandbox_command_assessment: None,

View File

@@ -147,7 +147,6 @@ pub async fn run_main(
developer_instructions: None,
compact_prompt: None,
include_apply_patch_tool: None,
include_view_image_tool: None,
show_raw_agent_reasoning: cli.oss.then_some(true),
tools_web_search_request: cli.web_search.then_some(true),
experimental_sandbox_command_assessment: None,

View File

@@ -312,12 +312,12 @@ Though using this option may also be necessary if you try to use Codex in enviro
### tools.\*
Use the optional `[tools]` table to toggle built-in tools that the agent may call. Both keys default to `false` (tools stay disabled) unless you opt in:
Use the optional `[tools]` table to toggle built-in tools that the agent may call. `web_search` stays off unless you opt in, while `view_image` is now enabled by default:
```toml
[tools]
web_search = true # allow Codex to issue first-party web searches without prompting you
view_image = true # let Codex attach local images (paths in your workspace) to the model request
view_image = false # disable image uploads (they're enabled by default)
```
`web_search` is also recognized under the legacy name `web_search_request`. The `view_image` toggle is useful when you want to include screenshots or diagrams from your repo without pasting them manually. Codex still respects sandboxing: it can only attach files inside the workspace roots you allow.
@@ -926,7 +926,7 @@ Valid values:
| `experimental_use_exec_command_tool` | boolean | Use experimental exec command tool. |
| `projects.<path>.trust_level` | string | Mark project/worktree as trusted (only `"trusted"` is recognized). |
| `tools.web_search` | boolean | Enable web search tool (alias: `web_search_request`) (default: false). |
| `tools.view_image` | boolean | Enable or disable the `view_image` tool so Codex can attach local image files from the workspace (default: true). |
| `forced_login_method` | `chatgpt` \| `api` | Only allow Codex to be used with ChatGPT or API keys. |
| `forced_chatgpt_workspace_id` | string (uuid) | Only allow Codex to be used with the specified ChatGPT workspace. |
| `cli_auth_credentials_store` | `file` \| `keyring` \| `auto` | Where to store CLI login credentials (default: `file`). |
| `tools.view_image` | boolean | Enable the `view_image` tool so Codex can attach local image files from the workspace (default: false). |