feat(core): add configurable log_dir (#10678)

Adds a top-level `log_dir` config key (defaults to `$CODEX_HOME/log`) so
one-off runs can redirect `codex-tui.log` via `-c`, e.g.:

  codex -c log_dir=./.codex-log

Also resolves relative paths in CLI `-c/--config` overrides for
`AbsolutePathBuf` values against the effective cwd (when available).

Tests:
- cargo test -p codex-core
This commit is contained in:
Josh McKinney 2026-02-04 17:23:30 -08:00 committed by GitHub
parent 0e8d359da9
commit cddfd1e675
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 67 additions and 5 deletions

View file

@ -1328,6 +1328,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",

View file

@ -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<History>,
/// Directory where Codex writes log files, for example `codex-tui.log`.
/// Defaults to `$CODEX_HOME/log`.
pub log_dir: Option<AbsolutePathBuf>,
/// 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<UriBasedFileOpener>,
@ -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<PathBuf> {
/// 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<PathBuf> {
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,

View file

@ -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,

View file

@ -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");

View file

@ -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

View file

@ -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.