A question worth prototyping

Could Codex hooks filter messages before you read them?

Matthew Berman asked why he can't attach his own prompt to scan incoming text messages for obvious scams and political content before he reads them. This page fact-checks whether an OpenAI Codex lifecycle hook could be part of the answer — and gives you runnable code for both Codex and Claude Code.

"It's ridiculous that I can't add a prompt to scan my text messages for obvious scams and political messages before I read them."Matthew Berman · LinkedIn post ↗
Why it's interesting

A user-controlled AI pre-read layer

A prompt tells a model what to do. A lifecycle hook decides when something runs. Put the two together and you get a programmable layer that sees content at a defined moment and can label, annotate, or block it before it reaches you. Matthew's question is really the bigger one: why shouldn't users attach their own AI policy to the information streams they receive?

The idea

Proposed architecture

Incoming message
OS / messaging integration
Codex hook (AI layer)
Classifier → fraud · political · neutral
User-defined action
Proposed architecture

The full pipeline is not something Codex does on its own. Codex never gains access to SMS or iMessage because a hook exists. A separate, permissioned OS/messaging integration must legally and technically expose the incoming message and feed it to the AI layer. Codex is only explored here as the programmable AI processing layer in the middle.

Verified today

What Codex can actually do

  • Codex has a hooks engine (added ~v0.114.0, March 2026) built toward "Claude Code hook parity".
  • UserPromptSubmit is a real, shipped hook event — it fires when a prompt is submitted into a Codex turn.
  • A command hook receives the turn input and can run any script — deterministic or model-backed.
  • On UserPromptSubmit, exit code 2 rejects the prompt and shows the hook's stderr; the hook can also add context.
  • Hooks are configured per layer: ~/.codex/hooks.json (user), <repo>/.codex/hooks.json (project), an inline [hooks] table in config.toml, or managed/enterprise layers via requirements.toml.
Not today

What Codex cannot do

  • It cannot read your phone's messages. A hook only sees what enters a Codex session.
  • It is not an iOS/Android SMS or iMessage filter, and installing a hook does not grant Messages access.
  • There is no official "plugin hooks" path like Claude Code's hooks/hooks.json — Codex hooks live in the config layers above.
  • Delivering messages into the classifier (with permission, low latency and privacy) is a separate product problem, not a Codex feature.
Step 1

The classifier

Keep the output deliberately tiny — the downstream system should receive a category, not an essay.

Analyze the current incoming text message and return exactly one category:

fraud
political
neutral

First, determine whether the message contains links, unsolicited offers,
requests for personal or sensitive information, suspicious urgency,
impersonation, payment requests, or known scam patterns.

Next, determine whether the message promotes political messages,
political parties, candidates, campaigns, ideological advocacy,
election-related content, or political calls to action.

Classification rules:
1. Return "fraud" if credible fraud or deception indicators are present.
2. Otherwise return "political" if the message contains political
   advocacy, campaigning, persuasion, or a political call to action.
3. Otherwise return "neutral".

Return only the category name.
Step 2

The smallest working Codex prototype

Configure the hook. This structure is the one Codex's hooks engine uses (confirm the current schema in the official Codex hooks docs ↗). Save it as .codex/hooks.json in your project (or ~/.codex/hooks.json globally):

{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python3 .codex/hooks/classify_message.py",
            "timeoutSec": 10,
            "statusMessage": "Scanning message"
          }
        ]
      }
    ]
  }
}

The script is the bridge: read the submitted text from the hook payload, classify it, and use exit 2 to block the categories you don't want. Inside a hook it stays silent when a message is allowed, because on exit 0 anything printed to stdout is added to the agent's context. The downloadable classify_message.py runs offline with English and Danish heuristics (no API key), and has an optional LLM mode. Core of it:

category = classify(message)          # "fraud" | "political" | "neutral"
as_hook = '"hook_event_name"' in raw  # silent inside a hook on exit 0

blocking = set(os.environ.get("NETSI_BLOCK", "").split(","))
if category in blocking:
    print(f"Message filter blocked this input (category: {category}).",
          file=sys.stderr)
    sys.exit(2)                       # UserPromptSubmit: exit 2 = reject
if not as_hook:
    print(category)                   # standalone: show the label

Verified  The test messages below were run through the actual downloadable script.

Download

Get the code — Codex and Claude Code

Two self-contained bundles. Both include the classifier, the prompt, the hook config and a README with install instructions. The classifier is identical; only the hook wiring differs, because Codex and Claude Code both expose a UserPromptSubmit hook.

🤖 Codex bundle

Hook config in .codex/hooks.json, script in .codex/hooks/.

⬇ Download codex-message-filter.zip

✳️ Claude Code bundle

Hook config in .claude/settings.json, script in .claude/hooks/.

⬇ Download claude-code-message-filter.zip
# Codex (project-local)
mkdir -p .codex/hooks
cp classify_message.py classifier-prompt.txt .codex/hooks/
cp hooks.json .codex/hooks.json

# Claude Code (project-local)
mkdir -p .claude/hooks
cp classify_message.py classifier-prompt.txt .claude/hooks/
# merge the "hooks" block from settings.json into .claude/settings.json
# (it blocks fraud,political by default — edit NETSI_BLOCK in the command)
Step 3

Test it with a few messages

Run the classifier directly, or submit each message as a prompt with the hook installed. With NETSI_BLOCK="fraud,political" (the Claude Code bundle's default; for Codex, export it before starting Codex), the fraud and political messages are rejected (exit 2); neutral passes.

MessageExpectedBlocked?
"Are we still on for lunch at 12?"neutralallowed
"Vote YES on Tuesday — re-elect the mayor!"politicalblocked
"Your bank account is locked. Verify now: http://bit.ly/x"fraudblocked
"Skal vi spise frokost kl. 12?"neutralallowed
"Stem JA på torsdag så borgmesteren bliver genvalgt"politicalblocked
"Din konto er spærret. Bekræft din identitet med MitID her"fraudblocked
echo '{"prompt":"Your account is locked, verify now http://bit.ly/x"}' \
  | python3 classify_message.py
# -> fraud   (exit code 2 when fraud is in NETSI_BLOCK)

echo "Are we still on for lunch?" | python3 classify_message.py
# -> neutral
Step 4

Decide what each category means

1

fraud

Quarantine, warn, or require an explicit reveal. Never silently delete — classifiers are wrong sometimes.

2

political

Move to a filtered view or label before display, per the user's own rule.

3

neutral

Continue through the normal inbox path.

4

uncertain

In production, add a confidence threshold or an "unknown" path even though the demo uses three labels.

Reality check

Limitations & false positives

Privacy

Privacy implications

From experiment to product

What a real SMS/iMessage filter would require

Proposed architecture

Everything below is the missing, non-Codex part of the pipeline:

  • A platform integration with permission to receive or forward messages — on iOS, an SMS filter / Message Filter app extension; on Android, the default SMS role or a carrier-level hook. These are constrained by each OS.
  • A secure bridge that passes the message to the AI layer with low latency and a safe fallback.
  • Clear user consent, on-device processing where possible, and a transparent, user-editable policy for each category.

The classification is the easy 10%. Secure, consented access to the message stream — and what the OS actually lets a user automate — is the hard 90%. That is exactly what makes Matthew's question a good one.

Sources

Where this was checked