[FEATURE] Mods: let a session.append hook redact or drop any block of a tool-result row (some blocks are restored by the engine)

Status Open
Reported on v2.1.296
Maintainer reply None cached
Activity 0 comments · opened Oct 11, 2026

Preflight Checklist

  • [x] I have searched existing requests and this feature hasn't been requested yet
  • [x] This is a single feature request (not multiple features)

Problem Statement

A mod that redacts secrets from tool results before they reach the model and the transcript (a session.append hook on door tool-result that rewrites the row's content and passes it on with next) cannot remove a secret from some parts of a row, because the engine restores those parts whatever the hook sends.

Observed on Claude Code 2.1.296 through the hooks-module test kit (claude plugin test, claude-code/testing):

  • A top-level search_result block beside the tool_result: rewriting it, or dropping it from content, has no effect. The block is stored as it arrived.
  • An unknown top-level block kind (and thinking / tool_use blocks at the top level of a tool-result row): restored the same way.
  • Extra fields on a tool_result block (anything beyond its known fields): restored even when the hook's rewrite of the same block's content lands.

We confirmed it is the engine, not our hook: a second test hook registered beneath ours, which removed the block or field outright, still saw it stored. By contrast, text blocks, nested search_result / document blocks inside tool_result.content, and text blocks' extra fields are rewritten or dropped as sent.

So a secret that lands in one of those parts reaches the transcript and the model in plaintext, and the hook has no way to stop it. SessionAppendResult does not let a plugin refuse a tool-result row either (only its own note appends). Our current behaviour is to detect the put-back after next (by comparing the stored row with what we sent), not count it as a redaction, and raise a notice that the block could not be redacted. That keeps the plugin honest but leaves the secret in place.

Proposed Solution

Let a session.append hook on door tool-result rewrite or drop any block in the row, and any field on a block, the same way it already can for text blocks and nested blocks. Redaction is the use case: a hook that has decided a block holds a secret needs the change to land.

If some blocks must stay pinned for the engine's own bookkeeping, either of these would also work for us:

  • Let the hook replace a pinned block's text-bearing fields (keeping its structural fields), or
  • Let the hook withhold the whole row's content for a tool-result (for example { withhold: reason }, replacing the content with a notice the model reads), so a plugin can fail closed when it cannot redact in place.

Alternative Solutions

  • Document which blocks and fields of a tool-result row are pinned, and expose that on the hook input (for example a pinned flag per block), so a plugin can at least know before next which redactions will not land.

Priority

Medium - Would be very helpful

Feature Category

Developer tools/SDK

Use Case Example

  1. A tool returns a result whose content holds a tool_result plus a top-level search_result block, and the search_result text includes an access token.
  2. The redaction mod's session.append hook replaces the token with [REDACTED:<kind>] in every block and calls next.
  3. Today the tool_result text is stored redacted, but the search_result is stored as it arrived, token included, and the model reads it.
  4. With the change, the stored row carries [REDACTED:<kind>] in both blocks.

Additional Context

  • Claude Code 2.1.296 (mods / hooks modules), macOS. Observed in the test kit; not yet confirmed in a live session.
  • Unrelated to settings PostToolUse hooks; this is about function hooks in a mod.
  • Related mods API requests: #99417, #91870.

View original on GitHub ↗