mirror of
https://github.com/openai/codex.git
synced 2026-11-02 08:19:52 +00:00
Add SessionEnd hooks for thread teardown (#33895)
## What changed - Add the `SessionEnd` hook event to hook configuration, protocol schemas, analytics, and the hooks browser. - Run matching hooks for root threads during shutdown, including app-server archive, delete, idle unload, and graceful shutdown. Flush the transcript first and provide the session ID, transcript path, working directory, and `reason: "other"`. - Keep teardown bounded: hook output is advisory, the default timeout is one second, configured timeouts are capped at three seconds, and async hooks run synchronously with a warning. ## Testing - Cover transcript availability, matcher selection, timeout normalization, ignored control output, subagent exclusion, archive/delete ordering, and graceful shutdown of multiple loaded threads. GitOrigin-RevId: 5d19a658677a137caf836ed5042dcb43f5eb6d1a
This commit is contained in:
1 parent
b9680065b0
commit
7bd44085e1
46 files changed
+930
-14
No files matched your search
@@ -365,6 +365,38 @@ pub(crate) async fn run_turn_stop_hooks(
|
||||
outcome
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", skip_all)]
|
||||
pub(crate) async fn run_session_end_hooks(sess: &Arc<Session>) {
|
||||
let hooks = sess.hooks();
|
||||
let preview_runs = hooks.preview_session_end();
|
||||
if preview_runs.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let turn_context = sess.new_default_turn().await;
|
||||
|
||||
// SessionEnd is root-only; ThreadSpawn uses SubagentStart/SubagentStop and other subagents
|
||||
// are internal implementation details.
|
||||
if matches!(&turn_context.session_source, SessionSource::SubAgent(_)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let request = codex_hooks::SessionEndRequest {
|
||||
session_id: sess.session_id().into(),
|
||||
turn_id: turn_context.sub_id.clone(),
|
||||
#[allow(deprecated)]
|
||||
cwd: turn_context.cwd.clone(),
|
||||
transcript_path: sess.hook_transcript_path().await,
|
||||
};
|
||||
if let Err(err) = sess.flush_rollout().await {
|
||||
tracing::warn!("failed to flush transcript before SessionEnd hook: {err}");
|
||||
}
|
||||
emit_hook_started_events(sess, &turn_context, preview_runs).await;
|
||||
|
||||
let outcome = hooks.run_session_end(request).await;
|
||||
emit_hook_completed_events(sess, &turn_context, outcome.hook_events).await;
|
||||
}
|
||||
|
||||
pub(crate) async fn run_pre_compact_hooks(
|
||||
sess: &Arc<Session>,
|
||||
turn_context: &Arc<TurnContext>,
|
||||
@@ -703,6 +735,7 @@ fn hook_run_metric_tags(run: &HookRunSummary) -> [(&'static str, &'static str);
|
||||
HookEventName::PreCompact => "PreCompact",
|
||||
HookEventName::PostCompact => "PostCompact",
|
||||
HookEventName::SessionStart => "SessionStart",
|
||||
HookEventName::SessionEnd => "SessionEnd",
|
||||
HookEventName::UserPromptSubmit => "UserPromptSubmit",
|
||||
HookEventName::SubagentStart => "SubagentStart",
|
||||
HookEventName::SubagentStop => "SubagentStop",
|
||||
|
||||
@@ -609,6 +609,8 @@ async fn shutdown_session_runtime(sess: &Arc<Session>) {
|
||||
}
|
||||
sess.services.mcp_runtime.shutdown().await;
|
||||
sess.guardian_review_session.shutdown().await;
|
||||
|
||||
crate::hook_runtime::run_session_end_hooks(sess).await;
|
||||
}
|
||||
|
||||
async fn emit_thread_stop_lifecycle(sess: &Session) {
|
||||
|
||||
Reference in new issue
Block a user