Expose code-mode parent provenance and terminal status to tool lifecycle hooks
What variant of Codex are you using?
CLI and App Server code mode. Observed on codex-cli 0.144.0; re-checked against the rust-v0.144.1 patch and current main source on 2026-07-11.
What feature would you like to see?
Please expose runtime-owned source/parent provenance and structured terminal status across the existing PreToolUse / PostToolUse lifecycle for tools invoked from code-mode exec JavaScript.
Codex already knows this relationship internally. Nested calls are dispatched with ToolCallSource::CodeMode { cell_id, runtime_tool_call_id }, but the hook payload reduces that to the same Bash-shaped { tool_use_id, tool_name, tool_input, tool_response } used for direct calls. A hook therefore cannot establish that a particular exec_command was actually invoked by a code-mode exec cell. It also cannot obtain the structured terminal exit code from PostToolUse.
A minimal hook contract could carry fields along these lines (exact naming is not important):
{
"tool_use_id": "opaque-runtime-correlation",
"tool_name": "Bash",
"tool_source": {
"type": "code_mode",
"parent_tool": "exec",
"parent_tool_use_id": "opaque-parent-correlation"
},
"tool_status": {
"state": "exited",
"exit_code": 0
}
}
tool_source should be present on both the start/pre event and its matching terminal/post event. tool_status belongs only on the terminal event. A command that initially yields a running session should remain open until write_stdin or the runtime's background watcher observes termination; that terminal event should retain the original nested tool ID and source correlation.
Acceptance criteria:
- Direct tool calls remain distinguishable from code-mode nested calls without inspecting JavaScript source.
- Every nested
exec_commandstart has exactly one matching terminal disposition, correlated by runtime-owned opaque values. - Immediate and yielded commands expose structured terminal state and exit code only after actual process termination.
- Continuation/poll completion retains the original nested tool ID and code-mode parent correlation.
- Missing, duplicate, reordered, or conflicting lifecycle events can be detected and failed closed by consumers.
- The contract does not require persisting JavaScript source, commands, outputs, paths, prompts, or rollout payloads.
- Hook documentation and focused tests cover direct, nested immediate, nested yielded, nonzero exit, cancellation, and malformed lifecycle cases.
This would allow a local hook/collector to classify an approved verifier command transiently, project only aggregate evidence, and discard the raw hook payload and opaque correlations.
Additional information
A throwaway in-memory state-model spike tested immediate success, nonzero exit, yielded completion, missing completion, completion without start, duplicate, reordered, conflicting, and privacy-bearing events.
The key finding was that a completion-only observation is insufficient: without a runtime-owned start plus correlated terminal event, a consumer cannot distinguish “no verifier ran” from “a verifier ran but completion was lost” without parsing model-authored JavaScript. The two-event contract handled the valid cases and failed closed for every anomaly while retaining only { verifier, version, proofClass, passed } after projection.
Relevant existing issues:
- #24175 requests structured shell status in
PostToolUse, but not code-mode source/parent correlation. - #23411 requests
PreToolUsefor the outer code-modeexec, but intentionally leavesPostToolUseand nested lifecycle provenance out of scope. - #16246 fixed/covered terminal
PostToolUseafterwrite_stdin, including reuse of the original tool call ID; this request asks that code-mode source correlation and structured status survive that same path.
Current source seams:
- Nested dispatch retains
cell_idandruntime_tool_call_id:codex-rs/core/src/tools/code_mode/mod.rs. - Hook dispatch currently forwards only tool ID/name/input/response:
codex-rs/core/src/tools/registry.rs. - Unified exec hook payload currently presents
HookToolName::bash()and response content:codex-rs/core/src/tools/handlers/unified_exec.rs. - App Server
commandExecutionhas structuredexitCode, but no nested code-mode parent linkage. - The diagnostic rollout-trace model has both kinds of data, but it is intentionally raw and is not an appropriate privacy-minimized collector contract.