diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index 138b71e2dca..a18fe3e25bf 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -1327,6 +1327,14 @@ "description": "System instructions.", "type": "string" }, + "log_dir": { + "allOf": [ + { + "$ref": "#/definitions/AbsolutePathBuf" + } + ], + "description": "Directory where Codex writes log files, for example `codex-tui.log`. Defaults to `$CODEX_HOME/log`." + }, "mcp_oauth_callback_port": { "description": "Optional fixed port for the local HTTP callback server used during MCP OAuth login. When unset, Codex will bind to an ephemeral port chosen by the OS.", "format": "uint16", diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index e22cfe65a1a..58768e584d9 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -271,6 +271,9 @@ pub struct Config { /// overridden by the `CODEX_HOME` environment variable). pub codex_home: PathBuf, + /// Directory where Codex writes log files (defaults to `$CODEX_HOME/log`). + pub log_dir: PathBuf, + /// Settings that govern if and what will be written to `~/.codex/history.jsonl`. pub history: History, @@ -896,6 +899,10 @@ pub struct ConfigToml { #[serde(default)] pub history: Option, + /// Directory where Codex writes log files, for example `codex-tui.log`. + /// Defaults to `$CODEX_HOME/log`. + pub log_dir: Option, + /// Optional URI-based file opener. If set, citations to files in the model /// output will be hyperlinked using the specified URI scheme. pub file_opener: Option, @@ -1560,6 +1567,16 @@ impl Config { let check_for_update_on_startup = cfg.check_for_update_on_startup.unwrap_or(true); + let log_dir = cfg + .log_dir + .as_ref() + .map(AbsolutePathBuf::to_path_buf) + .unwrap_or_else(|| { + let mut p = codex_home.clone(); + p.push("log"); + p + }); + // Ensure that every field of ConfigRequirements is applied to the final // Config. let ConfigRequirements { @@ -1626,6 +1643,7 @@ impl Config { tool_output_token_limit: cfg.tool_output_token_limit, agent_max_threads, codex_home, + log_dir, config_layer_stack, history, ephemeral: ephemeral.unwrap_or_default(), @@ -1816,9 +1834,7 @@ pub fn find_codex_home() -> std::io::Result { /// Returns the path to the folder where Codex logs are stored. Does not verify /// that the directory exists. pub fn log_dir(cfg: &Config) -> std::io::Result { - let mut p = cfg.codex_home.clone(); - p.push("log"); - Ok(p) + Ok(cfg.log_dir.clone()) } #[cfg(test)] @@ -3842,6 +3858,7 @@ model_verbosity = "high" tool_output_token_limit: None, agent_max_threads: DEFAULT_AGENT_MAX_THREADS, codex_home: fixture.codex_home(), + log_dir: fixture.codex_home().join("log"), config_layer_stack: Default::default(), history: History::default(), ephemeral: false, @@ -3927,6 +3944,7 @@ model_verbosity = "high" tool_output_token_limit: None, agent_max_threads: DEFAULT_AGENT_MAX_THREADS, codex_home: fixture.codex_home(), + log_dir: fixture.codex_home().join("log"), config_layer_stack: Default::default(), history: History::default(), ephemeral: false, @@ -4027,6 +4045,7 @@ model_verbosity = "high" tool_output_token_limit: None, agent_max_threads: DEFAULT_AGENT_MAX_THREADS, codex_home: fixture.codex_home(), + log_dir: fixture.codex_home().join("log"), config_layer_stack: Default::default(), history: History::default(), ephemeral: false, @@ -4113,6 +4132,7 @@ model_verbosity = "high" tool_output_token_limit: None, agent_max_threads: DEFAULT_AGENT_MAX_THREADS, codex_home: fixture.codex_home(), + log_dir: fixture.codex_home().join("log"), config_layer_stack: Default::default(), history: History::default(), ephemeral: false, diff --git a/codex-rs/core/src/config_loader/mod.rs b/codex-rs/core/src/config_loader/mod.rs index 28fda873571..0ae54111a47 100644 --- a/codex-rs/core/src/config_loader/mod.rs +++ b/codex-rs/core/src/config_loader/mod.rs @@ -144,7 +144,15 @@ pub async fn load_config_layers_state( let cli_overrides_layer = if cli_overrides.is_empty() { None } else { - Some(overrides::build_cli_overrides_layer(cli_overrides)) + let cli_overrides_layer = overrides::build_cli_overrides_layer(cli_overrides); + let base_dir = cwd + .as_ref() + .map(AbsolutePathBuf::as_path) + .unwrap_or(codex_home); + Some(resolve_relative_paths_in_config_toml( + cli_overrides_layer, + base_dir, + )?) }; // Include an entry for the "system" config folder, loading its config.toml, diff --git a/codex-rs/core/src/config_loader/tests.rs b/codex-rs/core/src/config_loader/tests.rs index 2d7e36d6efa..d68093c30f8 100644 --- a/codex-rs/core/src/config_loader/tests.rs +++ b/codex-rs/core/src/config_loader/tests.rs @@ -56,6 +56,30 @@ async fn make_config_for_test( .await } +#[tokio::test] +async fn cli_overrides_resolve_relative_paths_against_cwd() -> std::io::Result<()> { + let codex_home = tempdir().expect("tempdir"); + let cwd_dir = tempdir().expect("tempdir"); + let cwd_path = cwd_dir.path().to_path_buf(); + + let config = ConfigBuilder::default() + .codex_home(codex_home.path().to_path_buf()) + .cli_overrides(vec![( + "log_dir".to_string(), + TomlValue::String("run-logs".to_string()), + )]) + .harness_overrides(ConfigOverrides { + cwd: Some(cwd_path.clone()), + ..Default::default() + }) + .build() + .await?; + + let expected = AbsolutePathBuf::resolve_path_against_base("run-logs", cwd_path)?; + assert_eq!(config.log_dir, expected.to_path_buf()); + Ok(()) +} + #[tokio::test] async fn returns_config_error_for_invalid_user_config_toml() { let tmp = tempdir().expect("tempdir"); diff --git a/docs/install.md b/docs/install.md index 20d8b54a549..ef14f5d840b 100644 --- a/docs/install.md +++ b/docs/install.md @@ -51,7 +51,7 @@ cargo test --all-features Codex is written in Rust, so it honors the `RUST_LOG` environment variable to configure its logging behavior. -The TUI defaults to `RUST_LOG=codex_core=info,codex_tui=info,codex_rmcp_client=info` and log messages are written to `~/.codex/log/codex-tui.log`, so you can leave the following running in a separate terminal to monitor log messages as they are written: +The TUI defaults to `RUST_LOG=codex_core=info,codex_tui=info,codex_rmcp_client=info` and log messages are written to `~/.codex/log/codex-tui.log` by default. For a single run, you can override the log directory with `-c log_dir=...` (for example, `-c log_dir=./.codex-log`). ```bash tail -F ~/.codex/log/codex-tui.log diff --git a/docs/tui-stream-chunking-validation.md b/docs/tui-stream-chunking-validation.md index 26bd949da60..a31a7b62232 100644 --- a/docs/tui-stream-chunking-validation.md +++ b/docs/tui-stream-chunking-validation.md @@ -32,6 +32,8 @@ RUST_LOG='codex_tui::streaming::commit_tick=trace,codex_tui=info,codex_core=info ## Log capture process +Tip: for one-off measurements, run with `-c log_dir=...` to direct logs to a fresh directory and avoid mixing sessions. + 1. Record the current size of `~/.codex/log/codex-tui.log` as a start offset. 2. Run an interactive prompt that produces sustained streamed output. 3. Stop the run.