in codex-rs/hooks/src/events/stop.rs [202:371]
fn parse_completed(
handler: &ConfiguredHandler,
run_result: CommandRunResult,
turn_id: Option<String>,
) -> dispatcher::ParsedHandler<StopHandlerData> {
let mut entries = Vec::new();
let mut status = HookRunStatus::Completed;
let mut should_stop = false;
let mut stop_reason = None;
let mut should_block = false;
let mut block_reason = None;
let mut continuation_prompt = None;
let hook_event_name = match handler.event_name {
HookEventName::Stop | HookEventName::SubagentStop => handler.event_name,
event_name => {
panic!("expected stop hook event, got {event_name:?}");
}
};
match run_result.error.as_deref() {
Some(error) => {
status = HookRunStatus::Failed;
entries.push(HookOutputEntry {
kind: HookOutputEntryKind::Error,
text: error.to_string(),
});
}
None => match run_result.exit_code {
Some(0) => {
let trimmed_stdout = run_result.stdout.trim();
if trimmed_stdout.is_empty() {
} else if let Some(parsed) = match hook_event_name {
HookEventName::Stop => output_parser::parse_stop(&run_result.stdout),
HookEventName::SubagentStop => {
output_parser::parse_subagent_stop(&run_result.stdout)
}
_ => unreachable!("validated stop hook event"),
} {
if let Some(system_message) = parsed.universal.system_message {
entries.push(HookOutputEntry {
kind: HookOutputEntryKind::Warning,
text: system_message,
});
}
let _ = parsed.universal.suppress_output;
if !parsed.universal.continue_processing {
status = HookRunStatus::Stopped;
should_stop = true;
stop_reason = parsed.universal.stop_reason.clone();
if let Some(stop_reason_text) = parsed.universal.stop_reason {
entries.push(HookOutputEntry {
kind: HookOutputEntryKind::Stop,
text: stop_reason_text,
});
}
} else if let Some(invalid_block_reason) = parsed.invalid_block_reason {
status = HookRunStatus::Failed;
entries.push(HookOutputEntry {
kind: HookOutputEntryKind::Error,
text: invalid_block_reason,
});
} else if parsed.should_block {
if let Some(reason) =
parsed.reason.as_deref().and_then(common::trimmed_non_empty)
{
status = HookRunStatus::Blocked;
should_block = true;
block_reason = Some(reason.clone());
continuation_prompt = Some(reason.clone());
entries.push(HookOutputEntry {
kind: HookOutputEntryKind::Feedback,
text: reason,
});
} else {
status = HookRunStatus::Failed;
entries.push(HookOutputEntry {
kind: HookOutputEntryKind::Error,
text: match hook_event_name {
HookEventName::Stop => "Stop hook returned decision:block without a non-empty reason",
HookEventName::SubagentStop => "SubagentStop hook returned decision:block without a non-empty reason",
_ => unreachable!("validated stop hook event"),
}
.to_string(),
});
}
}
} else {
status = HookRunStatus::Failed;
entries.push(HookOutputEntry {
kind: HookOutputEntryKind::Error,
text: match hook_event_name {
HookEventName::Stop => "hook returned invalid stop hook JSON output",
HookEventName::SubagentStop => {
"hook returned invalid subagent stop hook JSON output"
}
_ => unreachable!("validated stop hook event"),
}
.to_string(),
});
}
}
Some(2) => {
if let Some(reason) = common::trimmed_non_empty(&run_result.stderr) {
status = HookRunStatus::Blocked;
should_block = true;
block_reason = Some(reason.clone());
continuation_prompt = Some(reason.clone());
entries.push(HookOutputEntry {
kind: HookOutputEntryKind::Feedback,
text: reason,
});
} else {
status = HookRunStatus::Failed;
entries.push(HookOutputEntry {
kind: HookOutputEntryKind::Error,
text: match hook_event_name {
HookEventName::Stop => {
"Stop hook exited with code 2 but did not write a continuation prompt to stderr"
}
HookEventName::SubagentStop => {
"SubagentStop hook exited with code 2 but did not write a continuation prompt to stderr"
}
_ => unreachable!("validated stop hook event"),
}
.to_string(),
});
}
}
Some(exit_code) => {
status = HookRunStatus::Failed;
entries.push(HookOutputEntry {
kind: HookOutputEntryKind::Error,
text: format!("hook exited with code {exit_code}"),
});
}
None => {
status = HookRunStatus::Failed;
entries.push(HookOutputEntry {
kind: HookOutputEntryKind::Error,
text: "hook exited without a status code".to_string(),
});
}
},
}
let completed = HookCompletedEvent {
turn_id,
run: dispatcher::completed_summary(handler, &run_result, status, entries),
};
let continuation_fragments = continuation_prompt
.map(|prompt| {
vec![HookPromptFragment::from_single_hook(
prompt,
completed.run.id.clone(),
)]
})
.unwrap_or_default();
dispatcher::ParsedHandler {
completed,
data: StopHandlerData {
should_stop,
stop_reason,
should_block,
block_reason,
continuation_fragments,
},
completion_order: 0,
}
}