[codex-analytics] preserve user thread source for exec threads (#23376)

## Why
- Follows #20949.
- The above moved `thread_source` attribution from the reducer to
explicit caller provided metadata
- The `codex exec` path still omitted this metadata, leaving
exec-created threads without `thread_source`


## What Changed
- Ensures exec threads are marked as user created (`thread_source =
"user"`)
- Preserves thread-source metadata in exec’s startup session event


## Verification
- Updated unit tests to validate exec `thread_source` propagation.
- `cargo +1.93.0 test -p codex-exec --manifest-path codex-rs/Cargo.toml`
- `cargo +1.93.1 build -p codex-cli --manifest-path codex-rs/Cargo.toml`
- Validated locally with a freshly built `codex exec` run:
  - Startup logs showed `thread_source: Some(User)`.
  - Rollout metadata recorded `"thread_source":"user"`.
This commit is contained in:
marksteinbrick-oai
2026-05-18 17:13:49 -07:00
committed by GitHub
Unverified
parent a66712c95d
commit 5696167fe8
2 changed files with 47 additions and 2 deletions
+6 -1
View File
@@ -40,6 +40,7 @@ use codex_app_server_protocol::ThreadReadResponse;
use codex_app_server_protocol::ThreadResumeParams;
use codex_app_server_protocol::ThreadResumeResponse;
use codex_app_server_protocol::ThreadSortKey;
use codex_app_server_protocol::ThreadSource;
use codex_app_server_protocol::ThreadSourceKind;
use codex_app_server_protocol::ThreadStartParams;
use codex_app_server_protocol::ThreadStartResponse;
@@ -974,6 +975,7 @@ fn thread_start_params_from_config(config: &Config) -> ThreadStartParams {
permissions,
config: config_request_overrides_from_config(config),
ephemeral: Some(config.ephemeral),
thread_source: Some(ThreadSource::User),
..ThreadStartParams::default()
}
}
@@ -1082,6 +1084,7 @@ fn session_configured_from_thread_start_response(
session_configured_from_thread_response(
&response.thread.session_id,
&response.thread.id,
response.thread.thread_source.map(Into::into),
response.thread.name.clone(),
response.thread.path.clone(),
response.model.clone(),
@@ -1103,6 +1106,7 @@ fn session_configured_from_thread_resume_response(
session_configured_from_thread_response(
&response.thread.session_id,
&response.thread.id,
response.thread.thread_source.map(Into::into),
response.thread.name.clone(),
response.thread.path.clone(),
response.model.clone(),
@@ -1133,6 +1137,7 @@ fn review_target_to_api(target: ReviewTarget) -> ApiReviewTarget {
fn session_configured_from_thread_response(
session_id: &str,
thread_id: &str,
thread_source: Option<codex_protocol::protocol::ThreadSource>,
thread_name: Option<String>,
rollout_path: Option<PathBuf>,
model: String,
@@ -1154,7 +1159,7 @@ fn session_configured_from_thread_response(
session_id,
thread_id,
forked_from_id: None,
thread_source: None,
thread_source,
thread_name,
model,
model_provider_id,
+41 -1
View File
@@ -458,6 +458,25 @@ async fn thread_start_params_include_review_policy_when_auto_review_is_enabled()
);
}
#[tokio::test]
async fn thread_start_params_include_user_thread_source() {
let codex_home = tempdir().expect("create temp codex home");
let cwd = tempdir().expect("create temp cwd");
let config = ConfigBuilder::default()
.codex_home(codex_home.path().to_path_buf())
.fallback_cwd(Some(cwd.path().to_path_buf()))
.build()
.await
.expect("build config");
let params = thread_start_params_from_config(&config);
assert_eq!(
params.thread_source,
Some(codex_app_server_protocol::ThreadSource::User)
);
}
#[test]
fn active_profile_selection_uses_profile_id_only() {
let selection = permissions_selection_from_active_profile(ActivePermissionProfile::new(
@@ -548,6 +567,27 @@ async fn session_configured_from_thread_response_uses_permission_profile_from_co
);
}
#[tokio::test]
async fn session_configured_from_thread_response_preserves_thread_source() {
let codex_home = tempdir().expect("create temp codex home");
let cwd = tempdir().expect("create temp cwd");
let config = ConfigBuilder::default()
.codex_home(codex_home.path().to_path_buf())
.fallback_cwd(Some(cwd.path().to_path_buf()))
.build()
.await
.expect("build config");
let response = sample_thread_start_response();
let event = session_configured_from_thread_start_response(&response, &config)
.expect("build bootstrap session configured event");
assert_eq!(
event.thread_source,
Some(codex_protocol::protocol::ThreadSource::User)
);
}
fn sample_thread_start_response() -> ThreadStartResponse {
ThreadStartResponse {
thread: codex_app_server_protocol::Thread {
@@ -564,7 +604,7 @@ fn sample_thread_start_response() -> ThreadStartResponse {
cwd: test_path_buf("/tmp").abs(),
cli_version: "0.0.0".to_string(),
source: codex_app_server_protocol::SessionSource::Cli,
thread_source: None,
thread_source: Some(codex_app_server_protocol::ThreadSource::User),
agent_nickname: None,
agent_role: None,
git_info: None,