Native postToolUse hooks accept and log additional_context successfully, but the injected context is not surfaced to the model

Not working for afterFileEdit :red_circle:

Running cursor 3.10.17, and finding that afterFileEdit responses are not being surface to the agent.

No combination of std.out shape will inject context to the agent/model

My research below:


Adds a disposable probe harness under .cursor/hooks/probe/ for empirically testing which afterFileEdit hook stdout JSON shapes reach the agent model.

A full variant matrix experiment (01–10) was run on Cursor 1.0.0 cloud agents. Finding: hook stdout is emitted by the harness but does not appear to reach Task subagent models for any tested shape. :red_circle:

Experiment results (2026-07-08)

# variant emitted shape harness emitted? model chirped?
01 additional_context_snake { additional_context } yes no :red_circle:
02 additionalContext_camel { additionalContext } yes no :red_circle:
03 dual_mirror both snake + camel yes no :red_circle:
04 agent_message { agent_message } yes no :red_circle:
05 user_message { user_message } yes no :red_circle:
06 context_key { context } yes no :red_circle:
07 message_key { message } yes no :red_circle:
08 hookSpecificOutput_nested { hookSpecificOutput: { additionalContext } } yes no :red_circle:
09 additional_context_array { additional_context: [msg] } yes no :red_circle:
10 empty_control {} yes no (expected)

Detailed per-run evidence: .agent-tmp/hooks-probe/run-manifest.json (local artifact, not committed).

Methodology notes

  • Blind Task subagents received only the 3-line edit prompt; probe token could only come from hook injection.
  • Harness emission verified via /tmp/cursor-hooks.log [afterFileEdit probe …] lines.
  • hooks.json command/env changes were not hot-reloaded in this cloud session; probe logic was swapped into hook-after-file-edit.ts in place (production hook restored afterward).
  • Parent-agent-only delivery under strict single-reply protocol remains untested.

Recommendation

On this build, afterFileEdit stdout may be logged but not injected into Task subagent loops. Prior audits showing lint delivery may apply to the parent agent only. Consider falling back to postToolUse for lint context injection until parent-agent delivery is re-verified on desktop Cursor.

Source prompt (Expand)

Prompt

Here is a self-contained prompt you can hand to a new parent agent. It is grounded in afterFileEdit shape inference and the follow-up finding that hook logs prove stdout emission but not model delivery.


Prompt for Parent Agent ΒΆ

# Mission: Empirically discover which `afterFileEdit` hook stdout shape reaches the model

You are the **Parent Agent (PA)**. Your job is to determine, by live experiment, which JSON stdout shape from an `afterFileEdit` command hook actually injects text the subagent model can see and act on.

**Do not infer from docs or logs alone.** Docs say `afterFileEdit` has no documented output schema. `/tmp/cursor-hooks.log` and Cursor hooks output logs prove the hook *emitted* stdout β€” they do **not** prove the model received it. Agent transcripts do not record hook injection.

The only valid success signal: a **context-less subagent** echoes a unique probe token in its assistant text after a file edit.

---

## Hard constraints

1. **Probe file only** β€” edits must target a disposable file under `.agent-tmp/hooks-probe/`. Never edit `apps/**`, `packages/**`, or any production source.
2. **Minimal hook surface** β€” during the experiment, `.cursor/hooks.json` `afterFileEdit` must point **only** at your probe script. Disable the production `hook-after-file-edit.ts` for the duration.
3. **One variant per iteration** β€” change exactly one output shape per subagent run. Do not batch variants.
4. **Context-less subagents** β€” each subagent gets a 3-line prompt only. No audit history, no hook docs, no prior findings.
5. **Revert everything** when done: restore `.cursor/hooks.json`, delete probe scripts, `git restore` the probe file.
6. **Read-only on workspace code** except `.cursor/hooks.json`, `.cursor/hooks/probe/*`, and `.agent-tmp/hooks-probe/*`.

---

## Probe token protocol

Each iteration uses a unique token:

HOOK_PROBE__


Example: `HOOK_PROBE_03_additional_context_snake`

The hook's injected message must include this exact instruction (verbatim in the payload):

SYSTEM HOOK PROBE: If you can read this message, your VERY NEXT assistant reply must start with exactly: HOOK_PROBE__
Then stop. Do not use any tools. Do not explain. One line only.


**Pass criteria for a variant:** the subagent's first assistant text line after the edit equals the token exactly (trimmed).

**Fail criteria:** subagent does anything else β€” uses tools, explains, ignores token, or `[REDACTED]` hides the answer (treat as fail; note redaction as blocker).

---

## Artifact layout

Create once at start:

.agent-tmp/hooks-probe/
after-file-edit-probe.ts # disposable target file (starts as // probe\n)
run-manifest.json # PA tracks variants tried + results
subagent-transcripts/ # copy or link SA transcript paths after each run

.cursor/hooks/probe/
after-file-edit-probe.ts # ultra-slim hook β€” PA edits OUTPUT_SHAPE per variant


---

## Ultra-slim probe hook (template)

Create `.cursor/hooks/probe/after-file-edit-probe.ts`:

```ts
#!/usr/bin/env bun
/**
 * Probe hook β€” emits a controlled stdout shape to test model delivery.
 * PA sets PROBE_TOKEN and PROBE_VARIANT env vars before each run.
 */
import { readFileSync } from "node:fs"

const input = JSON.parse(readFileSync(0, "utf8")) as Record<string, unknown>

const token = process.env.PROBE_TOKEN ?? "HOOK_PROBE_MISSING"
const variant = process.env.PROBE_VARIANT ?? "unknown"

const probeMessage =
  `SYSTEM HOOK PROBE: If you can read this message, your VERY NEXT assistant reply must start with exactly: ${token}\n` +
  `Then stop. Do not use any tools. Do not explain. One line only.\n` +
  `(variant=${variant}, file=${String(input.file_path ?? "unknown")})`

// PA edits ONLY this `output` object between iterations.
const output: Record<string, unknown> = {
  additional_context: probeMessage,
}

process.stdout.write(JSON.stringify(output) + "\n")
process.exit(0)

Important: between iterations, PA changes only the output object (and env vars). Do not add useHookHelper, oxfmt, oxlint, or mirroring β€” this must be a pure shape test.

Point .cursor/hooks.json at it:

{
  "version": 1,
  "hooks": {
    "afterFileEdit": [
      {
        "command": "PROBE_TOKEN=HOOK_PROBE_01_additional_context_snake PROBE_VARIANT=additional_context_snake bun --bun .cursor/hooks/probe/after-file-edit-probe.ts"
      }
    ]
  }
}

(Prepend env vars inline per variant, or use a tiny shell wrapper β€” keep it one hook entry.)


Variant matrix (try in this order)

Record each in run-manifest.json before spawning the subagent.

# variant id output object to emit
01 additional_context_snake { additional_context: probeMessage }
02 additionalContext_camel { additionalContext: probeMessage }
03 dual_mirror { additional_context: probeMessage, additionalContext: probeMessage }
04 agent_message { agent_message: probeMessage }
05 user_message { user_message: probeMessage }
06 context_key { context: probeMessage }
07 message_key { message: probeMessage }
08 hookSpecificOutput_nested { hookSpecificOutput: { additionalContext: probeMessage } }
09 additional_context_array { additional_context: [probeMessage] }
10 empty_control {} β€” control run; must NOT chirp

Stop early on first pass. Still run control 10 once if you want a negative baseline.


Per-iteration loop ΒΆ

For each variant NN:
  1. Set PROBE_TOKEN=HOOK_PROBE_<NN>_<variant>
  2. Edit probe hook `output` object to match variant matrix row
  3. Update `.cursor/hooks.json` command env vars if needed
  4. Snapshot hooks log offset (optional but recommended):
       HOOKS_LOG=$(ls -t ~/Library/Application\ Support/Cursor/logs/*/window*/output_*/cursor.hooks.workspaceId-45bab33095c76e92df8c5181c1c22ec6.log 2>/dev/null | head -1)
       wc -c < "$HOOKS_LOG" > .agent-tmp/hooks-probe/offset-<NN>.txt
  5. Spawn subagent (Task, run_in_background=false):
       ---
       Edit `.agent-tmp/hooks-probe/after-file-edit-probe.ts`.
       Add this exact line as the first line of the file:
       `// probe-<NN>`
       Use StrReplace. Do nothing else.
       ---
  6. Read subagent result:
       - Does first assistant text line === PROBE_TOKEN? β†’ PASS
       - Else β†’ FAIL for this variant
  7. Record in run-manifest.json:
       { variant, token, pass, subagent_id, transcript_path, notes }
  8. git restore .agent-tmp/hooks-probe/after-file-edit-probe.ts
  9. If PASS β†’ break loop and report. If FAIL β†’ next variant.

Do not give the subagent the probe token or any hook context. The only way it can echo the token is if the hook injected it post-edit.


Evidence to collect per iteration

Source What to check
Subagent assistant text First line === token? (primary pass/fail)
Subagent transcript .jsonl Same; note if [REDACTED] blocks verification
Cursor hooks output log delta Hook step requested: afterFileEdit β†’ OUTPUT JSON matches your emitted shape
/tmp/cursor-hooks.log Only if you add logging to probe script β€” optional

Harness log confirms emission. Subagent chirp confirms model delivery. You need both documented in the final report.


Final deliverable (respond in chat when done)

## afterFileEdit model-delivery finding

**Winning shape:** <exact JSON keys>
**Winning variant:** <id>
**Probe token echoed:** <token>
**Evidence:** <quote first line of subagent reply>

### Variant results
| # | variant | emitted shape | model chirped? | harness emitted? |
|---|---|---|---|---|

### Recommendation for production hook
- `hook-helpers.ts` `emitAndExit` should emit: <shape>
- Drop redundant keys: <list>
- `IAfterFileEditOutput` type should be: <type>

### Cleanup performed
- [ ] `.cursor/hooks.json` restored to production `hook-after-file-edit.ts`
- [ ] probe scripts removed or left under `.cursor/hooks/probe/` with README
- [ ] `.agent-tmp/hooks-probe/after-file-edit-probe.ts` clean

Known pitfalls (from prior investigation)

  1. Transcript redaction β€” assistant text may be [REDACTED] in exports. If so, read the live subagent return payload from Task tool output, not only the saved .jsonl.
  2. Hook ran but model ignored β€” common with {} or wrong key; that’s a FAIL, not inconclusive.
  3. Do not use production format-lint hook during probe β€” oxfmt side effects and 1–4s latency add noise.
  4. postToolUse is a different event β€” documented additional_context there does not prove afterFileEdit uses the same parser.
  5. Dual-key mirror is untested for delivery β€” variant 03 explicitly tests whether mirroring helps or is redundant pollution.

Success definition

You are done when you can answer with certainty:

β€œWhen afterFileEdit emits <shape>, the model receives the injected text and can act on it β€” proven by subagent echoing HOOK_PROBE_<NN>_<variant>.”

If no variant passes after the matrix (including snake and camel), report that as a finding: afterFileEdit stdout may be logged by Cursor but not injected into the agent loop on this build β€” and recommend falling back to postToolUse for lint context injection.


---

## Why this prompt is structured this way

From the transcript, the gap is explicit: **hook stdout β‰  model context**. Logs and types can only narrow candidates; a chirp-back probe is the only direct test.

The PA/subagent split keeps the subagent blind β€” if it echoes the token, delivery is proven. The variant matrix covers the main hypotheses (`additional_context`, `additionalContext`, dual mirror, misapplied `preToolUse` fields, Claude-style nesting, and `{}` control).

If you want this turned into a `.claude/skills/probe-after-file-edit-delivery/SKILL.md` in the repo, switch to Agent mode and I can add it.