through prompts.
We can describe the task, give instructions, and tell Codex what kind of result we expect. This allows us to control how the agent approaches its work.
But sometimes, prompting is not enough.
We may want to further customize the execution by running our own logic at different stages of a Codex session.
So, how can we do that?
The answer is Codex hooks.
In this post, we’ll explore the concept of hooks and understand where they fit into the agentic loop. Then, we’ll go through a concrete case study to demonstrate the concept.
1. Understanding Codex hooks
When Codex works on a task, it goes through an agentic loop.
For a new session, the user types in a prompt, Codex analyzes the problem, calls tools, and completes the task. You can think of this whole problem-solving trajectory as a lifecycle, and at different points in this lifecycle, Codex emits events with different event names:
SessionStart: emitted when a session begins;PreToolUse: emitted when Codex is about to call a tool;PostToolUse: emitted after the tool finishes;Stop: emitted when Codex is ready to finish its response;SessionEnd: emitted when the Codex session ends.
A Hook is the mechanism that allows us to attach our own logic to these events.
For example, we could use SessionStart hook to load additional context, or PreToolUse hook to inspect a command before it runs, or Stop hook to validate a result.
So, what does it mean to attach logic to an event?
Suppose we configure a hook for PreToolUse. Whenever Codex is about to call a tool, the hook runs a script. Codex passes information about that tool call to the script as part of the context.
Choosing PreToolUse only identifies a point in the lifecycle. Many different tool calls can occur at that point. As a result, we’d also need a matching rule to let us select the ones we actually care about. For example, we could run the script only when Codex is about to execute a shell command.
Therefore, there are three basic choices when configuring a hook:
- At which point in the lifecycle should it run?
- Under what conditions should it run at that point?
- What action should it execute?
In Codex, these correspond to the event, matcher, and handler. And this is the basic pattern behind Codex hooks.
2. Case study: Adding a quality gate to deep research
In this case study, we build a small deep research workflow with Codex.
Specifically, we’ll ask Codex to research recent trends in a given topic. Codex will conduct web searches and identify three important trends from the past 90 days. At the end, it should return a structured research brief.
To showcase the hook concept, we’ll add a quality check just before Codex finishes. It’ll verify that the brief contains enough sources and that those sources come from a reasonable variety of domains.
If the brief passes, Codex can finish. If it fails, the hook will send the problems back to Codex, and Codex will continue researching within the same run until it satisfies our checks.
2.1 Preparing the Research Task
We’ll start by preparing a prompt template:
# Deep research task
Research **{{TOPIC}}**.
Use sources published from **{{WINDOW_START}}** through **{{WINDOW_END}}**,
inclusive. Identify the three most important trends in that period and prepare
a concise, source-backed brief.
Return a concise, source-backed research brief that follows the supplied schema.
To ensure structured output, we also prepare a JSON schema:
{
"type": "object",
"additionalProperties": false,
"required": ["summary", "trends"],
"properties": {
"summary": {
"type": "string"
},
"trends": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["title", "summary", "sources"],
"properties": {
"title": {
"type": "string"
},
"summary": {
"type": "string"
},
"sources": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
}
We save this as schemas/research_brief.schema.json. Note that this is also the structure our hook expects.
2.2 Designing the Quality Gate
Next, we define what the hook should check.
Here, we check three things:
- Each trend should contain at least two sources.
- The brief should contain at least ten unique sources in total.
- Those sources must come from at least five unique domains.
We can only apply the checks after Codex has finished preparing it. That means a Stop hook is suitable here.
We first create the validation script in .codex/hooks/validate_research.py:
import json
import sys
from urllib.parse import urlparse
MIN_PER_TREND = 2
MIN_SOURCES = 10
MIN_DOMAINS = 5
event = json.load(sys.stdin)
brief = json.loads(event["last_assistant_message"])
errors = []
all_urls = set()
for number, trend in enumerate(brief["trends"], 1):
urls = set(trend["sources"])
all_urls.update(urls)
if len(urls) < MIN_PER_TREND:
errors.append(f"Trend {number} needs at least {MIN_PER_TREND} sources.")
domains = {
urlparse(url).netloc
for url in all_urls
}
if len(all_urls) < MIN_SOURCES:
errors.append(f"Add at least {MIN_SOURCES} unique sources.")
if len(domains) < MIN_DOMAINS:
errors.append(f"Use at least {MIN_DOMAINS} source domains.")
if errors:
message = "Research brief check failed:n- " + "n- ".join(errors)
result = {"decision": "block", "reason": message}
else:
result = {}
print(json.dumps(result))
When the Stop event is emitted, Codex passes in last_assistant_message, which follows the schema we defined earlier. Our script can then parse this response into a Python dictionary and iterate over the trends and collect their sources in a set.
Next, we use urlparse to extract the domain from each unique URL. After that, we can apply our checks.
If any check fails, the script would return a block decision together with the errors:
{
"decision": "block",
"reason": "Research brief check failed:n- Add at least 10 unique sources."
}
Note that for the Stop event, block doesn’t terminate the run; it just prevents Codex from finishing. Codex can use the feedback to improve the brief within the same run.
Now we need to define the hook to tell Codex when and how to execute it. We do this in .codex/hooks.json:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "python3 .codex/hooks/validate_research.py",
"commandWindows": "python .codex\hooks\validate_research.py"
}
]
}
]
}
}
Codex currently does not apply matchers to the Stop event. So we didn’t define any in the configuration above.
2.3 Running a Concrete Research Task
As a test, I asked Codex to research recent trends in data-center infrastructure:
{
"topic": "recent trends in data-center infrastructure",
"as_of": "2026-08-01",
"lookback_days": 90
}
After inserting these values into our prompt template, we save the rendered prompt to outputs/research_prompt.md.
Before the first run, you can open Codex in the project directory and use /hooks to review the hook.
In this case, we’ll run the task in headless mode with exec:
codex --search exec
--model gpt-5.6-sol
--json
--output-schema schemas/research_brief.schema.json
-o outputs/research_brief.json
-
< outputs/research_prompt.md
> outputs/run.jsonl
A couple of things worth mentioning:
exec: runs Codex non-interactively.--search: gives the agent access to web search.--model: selects the model used for the run.--output-schema: this is where we supply our pre-defined schema to constrain the agent output.-o: this means we save the agent’s response to the target location.--json: this makes Codex emit its execution events as JSONL. We redirect this event stream tooutputs/run.jsonl, which gives us a trace of the run.-: tells Codex to read the prompt from standard input.<: This operator suppliesoutputs/research_prompt.mdas that input.
During my test, I see that Codex first produced three trends supported by seven unique sources. Each trend had more than two sources, but the brief did not meet our overall requirement of ten.
Our Stop hook worked, as Codex received this feedback:
The brief needs broader corroboration. I’m adding at least three
independent, in-window sources while preserving the same three
evidence-supported trends.
After another round, Codex finally produced an updated brief with 12 unique sources from 10 domains.
The final brief identified three major trends: the rise of gigawatt-scale AI campuses, power access and permitting as infrastructure constraints, and the shift toward liquid cooling.
The hook ran again, but this time it allowed Codex to finish. The outcome is saved to outputs/research_brief.json.
3. When Hooks Are Useful
In our case study, we showed how to use a Stop hook to validate a completed result. The same design process also applies to other lifecycle events.
For SessionStart hook, it is useful when we need to load context when a session begins. If we need to inspect an operation before it happens, we can use PreToolUse hook. If we want to process the result of a tool call, we can use PostToolUse hook.
When designing a hook, ask yourself three questions:
- At which point in the lifecycle should it run?
- Under what conditions should it run at that point?
- What action should it execute?
This is how you can add deterministic logic around the Codex execution.

