hooks: use new session IDs instead of thread IDs for hooks, apply parent's session ID to subagents' hooks (#22268)

## Why

hook semantics treat `session_id` as shared across a root session and
its subagents. Codex hooks were still emitting the current thread ID,
which made spawned agents look like independent sessions and made it
harder for hook integrations to correlate work across a root thread and
its spawned helpers

This change makes hooks use Codex's existing shared session identity so
hook `session_id` matches the root-thread session across spawned
subagents.

## What Changed

- switch hook payloads to use the existing shared session identity from
core instead of the current thread ID
- cover all hook surfaces that expose `session_id`, including
`SessionStart`, tool hooks, compact hooks, prompt-submit hooks, stop
hooks, and legacy after-agent dispatch
This commit is contained in:
Andrei Eternal
2026-05-12 19:05:10 -07:00
committed by GitHub
parent e2eb7c30fe
commit fbfbfe5fc5
4 changed files with 11 additions and 12 deletions

View File

@@ -115,7 +115,7 @@ pub(crate) async fn run_pending_session_start_hooks(
};
let request = codex_hooks::SessionStartRequest {
session_id: sess.conversation_id,
session_id: sess.session_id().into(),
cwd: turn_context.cwd.clone(),
transcript_path: sess.hook_transcript_path().await,
model: turn_context.model_info.slug.clone(),
@@ -148,7 +148,7 @@ pub(crate) async fn run_pre_tool_use_hooks(
tool_input: &Value,
) -> PreToolUseHookResult {
let request = PreToolUseRequest {
session_id: sess.conversation_id,
session_id: sess.session_id().into(),
turn_id: turn_context.sub_id.clone(),
cwd: turn_context.cwd.clone(),
transcript_path: sess.hook_transcript_path().await,
@@ -207,7 +207,7 @@ pub(crate) async fn run_permission_request_hooks(
payload: PermissionRequestPayload,
) -> Option<PermissionRequestDecision> {
let request = PermissionRequestRequest {
session_id: sess.conversation_id,
session_id: sess.session_id().into(),
turn_id: turn_context.sub_id.clone(),
cwd: turn_context.cwd.to_path_buf(),
transcript_path: sess.hook_transcript_path().await,
@@ -247,7 +247,7 @@ pub(crate) async fn run_post_tool_use_hooks(
tool_response: Value,
) -> PostToolUseOutcome {
let request = PostToolUseRequest {
session_id: sess.conversation_id,
session_id: sess.session_id().into(),
turn_id: turn_context.sub_id.clone(),
cwd: turn_context.cwd.clone(),
transcript_path: sess.hook_transcript_path().await,
@@ -274,7 +274,7 @@ pub(crate) async fn run_pre_compact_hooks(
trigger: CompactionTrigger,
) -> PreCompactHookOutcome {
let request = codex_hooks::PreCompactRequest {
session_id: sess.conversation_id,
session_id: sess.session_id().into(),
turn_id: turn_context.sub_id.clone(),
cwd: turn_context.cwd.clone(),
transcript_path: sess.hook_transcript_path().await,
@@ -311,7 +311,7 @@ pub(crate) async fn run_post_compact_hooks(
trigger: CompactionTrigger,
) -> PostCompactHookOutcome {
let request = codex_hooks::PostCompactRequest {
session_id: sess.conversation_id,
session_id: sess.session_id().into(),
turn_id: turn_context.sub_id.clone(),
cwd: turn_context.cwd.clone(),
transcript_path: sess.hook_transcript_path().await,
@@ -336,7 +336,7 @@ pub(crate) async fn run_user_prompt_submit_hooks(
prompt: String,
) -> HookRuntimeOutcome {
let request = UserPromptSubmitRequest {
session_id: sess.conversation_id,
session_id: sess.session_id().into(),
turn_id: turn_context.sub_id.clone(),
cwd: turn_context.cwd.clone(),
transcript_path: sess.hook_transcript_path().await,

View File

@@ -2331,7 +2331,7 @@ async fn permission_request_hook_allows_mcp_tool_call() {
assert_eq!(
inputs,
vec![serde_json::json!({
"session_id": session.conversation_id,
"session_id": session.session_id(),
"turn_id": "turn_id",
"cwd": turn_context.cwd,
"transcript_path": null,
@@ -2391,7 +2391,7 @@ async fn permission_request_hook_uses_hook_tool_name_without_metadata() {
assert_eq!(
inputs,
vec![serde_json::json!({
"session_id": session.conversation_id,
"session_id": session.session_id(),
"turn_id": "turn_id",
"cwd": turn_context.cwd,
"transcript_path": null,

View File

@@ -523,7 +523,7 @@ pub(crate) async fn run_turn(
}
.to_string();
let stop_request = codex_hooks::StopRequest {
session_id: sess.conversation_id,
session_id: sess.session_id().into(),
turn_id: turn_context.sub_id.clone(),
cwd: turn_context.cwd.clone(),
transcript_path: sess.hook_transcript_path().await,
@@ -573,7 +573,7 @@ pub(crate) async fn run_turn(
let hook_outcomes = sess
.hooks()
.dispatch(HookPayload {
session_id: sess.conversation_id,
session_id: sess.session_id().into(),
cwd: turn_context.cwd.clone(),
client: turn_context.app_server_client_name.clone(),
triggered_at: chrono::Utc::now(),

View File

@@ -628,7 +628,6 @@ fn unsupported_tool_call_message(payload: &ToolPayload, tool_name: &ToolName) ->
_ => format!("unsupported call: {tool_name}"),
}
}
#[cfg(test)]
#[path = "registry_tests.rs"]
mod tests;