Skip to content

Connect harness tool interception hooks - #2229

Open
xeophon wants to merge 1 commit into
agent/intercept-terminalfrom
agent/intercept-harness-hooks
Open

Connect harness tool interception hooks#2229
xeophon wants to merge 1 commit into
agent/intercept-terminalfrom
agent/intercept-harness-hooks

Conversation

@xeophon

@xeophon xeophon commented Aug 3, 2026

Copy link
Copy Markdown
Member

Overview

Connects harness-owned tool calls and results to Verifiers interception.

A response hook can return a vf.ToolMessage for a tool call the model just proposed. The harness then blocks that call before execution and gives the synthetic result back to the agent as a normal tool message.

What this PR adds

  • One authenticated /tool callback for pre-execution calls and post-execution results, keyed by the native tool-call ID.
  • Direct interception in the Bash harness and native PreToolUse / post-tool hooks for Claude Code and Codex.
  • Allow, rewrite, or terminate decisions using the full branch-local Trace.
  • Canonical graph commits for the tool message the harness actually sends back to the model.
  • Unsupported harnesses fail closed before tool execution, and tasks without interceptors do not set up hooks or callback clients.

The original assistant tool call stays in the trace, followed by the synthetic tool result and the agent's continuation. Codex decorates denied commands with its native blocked-command text, while Bash and Claude return the synthetic text directly.

Stack

  1. Add interception transport and replay #2164 adds the model interception transport and buffered delivery.
  2. Add typed model interception #2165 adds the public @vf.on_request and @vf.on_response hooks.
  3. Make interception termination final #2166 makes vf.Terminate final for rollout and environment scoring.
  4. This PR connects Bash, Claude Code, and Codex tool calls and results to those hooks.
  5. Add interception example environments #2178 adds small example environments.

Note

Medium Risk
Touches interception, trace graph commits, and agent execution paths; Codex interception enables hook trust bypass, and incorrect /tool graph alignment could mis-attribute training data.

Overview
Harness-owned tools now flow through Verifiers interception when a task defines @on_response / @on_request interceptors. Response hooks can return a ToolMessage to skip execution and supply a synthetic result; the harness never runs that call and the agent sees a normal tool reply.

A new POST /tool endpoint on the interception server handles before (pre-execution) and after (post-result) phases with allow, rewrite, or terminate decisions. Callbacks are serialized with a per-session lock so parallel tool calls extend one graph branch correctly. Tool messages committed via /tool get canonical graph updates, including backfilling token ids for nodes created before the renderer ran.

Bash posts to /tool around each tool invocation (--tool-interception-url). Claude Code and Codex install a shared tool_hook.py bridge for native PreToolUse/PostToolUse events; Codex enables hooks with --dangerously-bypass-hook-trust when interception is on.

Rollouts only enable hook setup, expose /tool in the runtime network allowlist, and pass the URL when interceptors exist. SUPPORTS_TOOL_INTERCEPTION gates harness capability; graph MoE routing attribution now keys nodes by explicit (node_id, token_start) pairs to support /tool-committed messages.

Reviewed by Cursor Bugbot for commit a2b9c78. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Connect harness tool interception hooks for bash, Claude Code, and Codex harnesses

  • Adds a /tool endpoint to InterceptionServer that authenticates, validates, and responds to ToolInterceptionRequest events with allow, rewrite, or terminate decisions.
  • Introduces setup_tool_interception and SUPPORTS_TOOL_INTERCEPTION to the base Harness class; bash, Claude Code, and Codex harnesses implement this to configure native hook mechanisms at launch.
  • Adds a shared tool_hook.py script uploaded into runtimes for Claude Code and Codex; it reads hook events from stdin and forwards them to the interception server.
  • Rollout constructs the /tool URL when interceptors are present, adds it to the egress allowlist, and passes it into harness.run/launch/resume.
  • Response interceptors in RolloutSession can now synthesize ToolMessage results or terminate when the harness supports tool interception.
  • Risk: --dangerously-bypass-hook-trust is set for Codex when hooks are enabled; requires Python ≥3.11 in the bash program.

Macroscope summarized a2b9c78.

Comment thread verifiers/v1/harnesses/null/program.py Outdated
Comment thread verifiers/v1/interception/server.py Outdated
@xeophon
xeophon force-pushed the agent/intercept-harness-hooks branch from 10ce46b to 4ffec10 Compare August 3, 2026 13:02
Comment thread verifiers/v1/harnesses/codex/harness.py

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 10ce46b2e3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread verifiers/v1/harness.py Outdated
Comment thread verifiers/v1/harnesses/_tool_hook.py Outdated
@xeophon
xeophon force-pushed the agent/intercept-harness-hooks branch from 4ffec10 to 36dd49b Compare August 3, 2026 13:07

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 36dd49b67d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread verifiers/v1/harnesses/pi/harness.py Outdated
@macroscopeapp

macroscopeapp Bot commented Aug 3, 2026

Copy link
Copy Markdown

Approvability

Verdict: Needs human review

2 blocking correctness issues found. This PR adds a new tool interception feature with significant complexity across multiple files and harnesses. Additionally, there are multiple unresolved HIGH severity review comments identifying potential bugs in trace management, protocol handling, and race conditions that require human attention.

You can customize Macroscope's approvability policy. Learn more.

@xeophon
xeophon force-pushed the agent/intercept-harness-hooks branch from 36dd49b to c1084d9 Compare August 3, 2026 13:15
Comment thread verifiers/v1/harnesses/_tool_hook.py Outdated
Comment thread verifiers/v1/harnesses/_tool_hook.py Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c1084d94da

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread verifiers/v1/harnesses/claude_code/harness.py
Comment thread verifiers/v1/interception/server.py Outdated
Comment thread verifiers/v1/harnesses/_tool_hook.py Outdated
@xeophon
xeophon force-pushed the agent/intercept-harness-hooks branch from c1084d9 to 55ea9c6 Compare August 3, 2026 13:23
Comment thread verifiers/v1/harnesses/_tool_hook.py Outdated
Comment thread verifiers/v1/harnesses/_tool_hook.py Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 55ea9c6db4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread verifiers/v1/harnesses/_tool_hook.py Outdated
Comment thread verifiers/v1/harnesses/_tool_hook.py Outdated
Comment thread verifiers/v1/harnesses/_tool_hook.py Outdated
@xeophon
xeophon force-pushed the agent/intercept-harness-hooks branch from 55ea9c6 to 3b50905 Compare August 3, 2026 13:32
Comment thread verifiers/v1/harnesses/codex/harness.py

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3b50905efb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread verifiers/v1/rollout.py Outdated
Comment thread verifiers/v1/harnesses/_tool_hook.py Outdated
@xeophon
xeophon force-pushed the agent/intercept-harness-hooks branch from 3b50905 to 142f853 Compare August 3, 2026 13:40
Comment thread verifiers/v1/harnesses/_tool_hook.py Outdated
@xeophon
xeophon force-pushed the agent/intercept-harness-hooks branch from e1e1512 to 990a6c0 Compare August 5, 2026 08:12

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 990a6c02eb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread verifiers/v1/interception/server.py
@xeophon
xeophon force-pushed the agent/intercept-harness-hooks branch from 990a6c0 to b74aaba Compare August 5, 2026 08:24
Comment thread verifiers/v1/interception/server.py
Comment thread verifiers/v1/interception/server.py

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b74aaba6fc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread verifiers/v1/interception/server.py
@xeophon
xeophon force-pushed the agent/intercept-harness-hooks branch from b74aaba to 7a4870d Compare August 5, 2026 09:08
Comment thread verifiers/v1/interception/server.py
@xeophon
xeophon force-pushed the agent/intercept-harness-hooks branch from 7a4870d to 79d1156 Compare August 5, 2026 09:12
Comment thread verifiers/v1/harness.py Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 79d115619f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread verifiers/v1/interception/server.py Outdated
@xeophon
xeophon force-pushed the agent/intercept-harness-hooks branch from 79d1156 to b984ce9 Compare August 5, 2026 12:19

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b984ce973c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread verifiers/v1/harnesses/codex/harness.py Outdated
@xeophon
xeophon force-pushed the agent/intercept-harness-hooks branch from b984ce9 to 19c94aa Compare August 5, 2026 13:12
Comment thread verifiers/v1/harnesses/tool_hook.py
@xeophon
xeophon force-pushed the agent/intercept-harness-hooks branch from 19c94aa to 35e7e4e Compare August 5, 2026 13:17

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9bb042cfcb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

rewritten = message
if records and not isinstance(rewritten.content, str):
raise TaskError("harness tool hooks can only rewrite plain text")
graph.prepare_turn(session.trace, [*branch, rewritten]).commit_prompt()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve tool_call_id when committing hook rewrites

When an @on_request handler rebuilds a ToolMessage for a harness hook and accidentally changes tool_call_id, this commits the changed identity to the trace even though the hook response can only return replacement content to the harness. Bash/Claude/Codex will continue the conversation with the native original tool_call_id, and graph.message_hash() includes that id, so the next model request won't reuse this node and can re-run the same tool-result interceptor or hit the non-hook rewrite error; reject identity changes or commit the original id with the rewritten content.

Useful? React with 👍 / 👎.

graph.prepare_turn(session.trace, [*branch, rewritten]).commit_prompt()
session.trace.interceptions.extend(records)
if termination is not None:
session.terminate(termination)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sealed trace mutated after release

Medium Severity

handle_tool only checks session.released before waiting on tool_lock, then commits after await intercept_request with no re-check. Cancellation is delivered at await points, so the sync commit_prompt / terminate path can still run after release() seals the trace. handle_request already re-checks released before committing for this reason.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9bb042c. Configure here.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 134ed86. Configure here.

ToolInterceptionDecision(
action="rewrite", content=content_text(rewritten.content)
).model_dump()
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pending tool rewrite skips commit

High Severity

When a response hook supplies a pending ToolMessage, the before-phase path returns rewrite and records interceptions but never commit_prompts that result. Later tools in the same turn still commit against a branch that omits the synthetic message, so the harness conversation and graph diverge and the next model request can fork.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 134ed86. Configure here.

Comment on lines +131 to +133
content=native
if native is None or isinstance(native, str)
else json.dumps(native),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High harnesses/tool_hook.py:131

When a PostToolUse event has tool_response set to JSON null, main sends content: null in the ToolInterceptionRequest instead of serializing it to the string "null". The /tool handler rejects after-execution requests with null content ("tool result content is required after execution"), so the hook fails and interrupts the rollout even though the tool result is valid. The content field is set to native directly when native is None, bypassing json.dumps. Consider serializing None to "null" so the callback receives a string payload.

Suggested change
content=native
if native is None or isinstance(native, str)
else json.dumps(native),
content=json.dumps(native)
if native is not None and not isinstance(native, str)
else None
if native is None
else native,
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @verifiers/v1/harnesses/tool_hook.py around lines 131-133:

When a `PostToolUse` event has `tool_response` set to JSON `null`, `main` sends `content: null` in the `ToolInterceptionRequest` instead of serializing it to the string `"null"`. The `/tool` handler rejects after-execution requests with null content ("tool result content is required after execution"), so the hook fails and interrupts the rollout even though the tool result is valid. The `content` field is set to `native` directly when `native is None`, bypassing `json.dumps`. Consider serializing `None` to `"null"` so the callback receives a string payload.

event: [
{"hooks": [{"type": "command", "command": command}]}
]
for event in ("PreToolUse", "PostToolUse")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High codex/harness.py:271

When tool_interception_url is set, the same hook command is registered for both PreToolUse and PostToolUse. If the interception server returns action="terminate" during PreToolUse, tool_hook.py emits {"continue": false, "stopReason": ...}, but Codex's PreToolUse hook contract does not recognize those stop fields. Codex treats the hook as failed and proceeds with the tool call anyway, so a termination decision made before execution does not prevent the tool and its side effects from running. Consider emitting a Codex-compatible PreToolUse deny response (or not registering the terminate path for PreToolUse) so the tool call is actually blocked.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @verifiers/v1/harnesses/codex/harness.py around line 271:

When `tool_interception_url` is set, the same hook command is registered for both `PreToolUse` and `PostToolUse`. If the interception server returns `action="terminate"` during `PreToolUse`, `tool_hook.py` emits `{"continue": false, "stopReason": ...}`, but Codex's `PreToolUse` hook contract does not recognize those stop fields. Codex treats the hook as failed and proceeds with the tool call anyway, so a termination decision made before execution does not prevent the tool and its side effects from running. Consider emitting a Codex-compatible `PreToolUse` deny response (or not registering the terminate path for `PreToolUse`) so the tool call is actually blocked.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a2b9c78c5a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +157 to +162
elif decision.action == "rewrite" and before:
output: BaseModel = PreToolUseOutput(
hook_specific_output=PreToolUseDecision(
permission_decision_reason=cast(str, decision.content)
)
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve synthetic Codex tool results

When a response interceptor returns a ToolMessage for Codex, /tool reports that as a rewrite during PreToolUse, but this branch translates it into permissionDecision: deny. I checked the Codex hooks reference (https://learn.chatgpt.com/docs/hooks#tool-calls-from-code-mode): a blocked PreToolUse rejects code-mode tool promises and is surfaced as a denial, so Codex sees a failed tool instead of the synthetic output promised by @on_response. This affects Codex tasks that try to skip/synthesize a tool call; only advertise pre-tool rewrites for harnesses that can deliver a real replacement result, or use a native synthetic-output mechanism.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant