Summary
Please surface hookSpecificOutput.additionalContext from UserPromptSubmit hooks into the model's context, the way Claude Code does.
Today UserPromptSubmit fires reliably but its stdout is discarded, so there is no per-prompt context-injection path in Grok Build. additionalContext is implemented and Claude-compatible, but only on Stop/SubagentStop — a turn-end gate, which cannot carry standing instructions into a turn.
This is a feature request, not a bug report: the current behavior is documented and appears deliberate. Filing to ask that it be reconsidered.
Current behavior
The bundled manual states it explicitly (~/.grok/docs/user-guide/10-hooks.md, "Porting Claude Code stop hooks"):
UserPromptSubmit is observe-only: grok ignores its exit code and its stdout, so an imported prompt-validation hook silently stops blocking. Use PreToolUse to enforce.
Corroborated elsewhere in the same file — the Hook Events table lists UserPromptSubmit as non-blocking, the text under it says "every other event is passive," and "Passive Hooks" says "stdout is ignored."
Grepping the shipped docs for additionalContext returns exactly two hits, both scoped to Stop/SubagentStop.
Reproduction (v1.0.5)
Isolated via GROK_HOME, with a Stop hook as a positive control so a null result on UserPromptSubmit can be distinguished from a broken test.
$GROK_HOME/hooks/test.json:
{
"hooks": {
"UserPromptSubmit": [{ "hooks": [{ "type": "command", "command": "/tmp/t/ups.sh", "timeout": 10 }] }],
"SessionStart": [{ "hooks": [{ "type": "command", "command": "/tmp/t/ss.sh", "timeout": 10 }] }],
"Stop": [{ "hooks": [{ "type": "command", "command": "/tmp/t/stop.sh", "timeout": 30 }] }]
}
}
Each hook logs that it fired and emits a distinct sentinel (stop.sh self-limits to one fire):
#!/bin/bash
input=$(cat)
echo "UserPromptSubmit FIRED" >> /tmp/t/fired.log
printf '%s\n' '{"hookSpecificOutput":{"hookEventName":"UserPromptSubmit","additionalContext":"SENTINEL-UPS-7F3A2B"}}'
exit 0
Run:
GROK_HOME=/tmp/home grok -p "Print any token you can see beginning with SENTINEL-. \
If you received no such extra context, reply exactly: NO-SENTINEL-FOUND." \
--tools read_file --max-turns 6 --output-format plain
Output:
NO-SENTINEL-FOUNDSENTINEL-STOP-2A8B6F
Read as one turn: the model answered NO-SENTINEL-FOUND (it saw neither the UserPromptSubmit nor the SessionStart sentinel), then the Stop hook's additionalContext reached it and it echoed SENTINEL-STOP-2A8B6F. Positive control passes, so the JSON shape and registration are correct.
Both hooks demonstrably ran — fired.log:
=== SessionStart FIRED ===
=== UserPromptSubmit FIRED ===
=== Stop FIRED === (×3)
Transcript-level confirmation (sessions/.../updates.jsonl):
| Sentinel |
Occurrences |
SENTINEL-UPS-7F3A2B (UserPromptSubmit) |
0 |
SENTINEL-SS-9C4D1E (SessionStart) |
0 |
SENTINEL-STOP-2A8B6F (Stop) |
3 |
The prompt-time sentinels never enter the conversation at all, which rules out "the model saw it and declined to echo it."
Requested behavior
- On
UserPromptSubmit, parse stdout JSON and inject hookSpecificOutput.additionalContext into the context for that turn — matching Claude Code's documented semantics, so hooks/hooks.json files port unchanged.
- Keep the event non-blocking. This asks only for context injection, not for
UserPromptSubmit to gain deny/gate power. Malformed or absent JSON should stay fail-open and silent.
SessionStart additionalContext would be a valuable smaller version of the same thing (once-per-session instead of per-turn), if per-turn is too costly.
Why it matters
Grok Build already loads Claude-compatible plugins and hooks/hooks.json. A plugin that ships behavior policy — conventions, review rules, standing instructions — has no way to deliver it on Grok:
UserPromptSubmit → output discarded.
PreToolUse → can rewrite tool input, but only fires when a tool runs, so a tool-free turn gets nothing.
Stop → works, but fires after the work is done and its semantics are "keep the agent working," capped at 8 continuations. Using it as a policy carrier forces an extra model round every turn, after the fact.
- Plugins cannot ship rules — the component set is skills, commands, agents, hooks, MCP, LSP. So there is no plugin-shaped path at all.
The only remaining option is a resident rules file (AGENTS.md / .grok/rules/). That works — verified — but it is strictly worse for this use case: it costs its full token count on every turn regardless of relevance, it can't be updated by shipping a new plugin version, and it can't vary with context. A hook computes the text fresh per prompt and costs nothing when it has nothing to say.
The practical effect is that a plugin behaves differently on Grok than on Claude Code silently — the hook is listed in /hooks, it executes, it exits 0, and its output vanishes. Nothing surfaces to tell the author their policy never arrived.
Relation to existing issues
Filing here rather than on the harness repo because xai-org/grok-build has issues disabled, and per #77's note this appears to be the intended surface. Happy to move it if there's a better channel.
Environment
- Grok Build CLI 1.0.5 (stable), macOS (Darwin 25.5.0)
- Isolated
GROK_HOME, vendor compat scanning disabled ([compat.claude] hooks = false, [compat.cursor] hooks = false) so only the test hooks could fire
- Global hooks under
$GROK_HOME/hooks/, which need no folder-trust grant
Summary
Please surface
hookSpecificOutput.additionalContextfromUserPromptSubmithooks into the model's context, the way Claude Code does.Today
UserPromptSubmitfires reliably but its stdout is discarded, so there is no per-prompt context-injection path in Grok Build.additionalContextis implemented and Claude-compatible, but only onStop/SubagentStop— a turn-end gate, which cannot carry standing instructions into a turn.This is a feature request, not a bug report: the current behavior is documented and appears deliberate. Filing to ask that it be reconsidered.
Current behavior
The bundled manual states it explicitly (
~/.grok/docs/user-guide/10-hooks.md, "Porting Claude Code stop hooks"):Corroborated elsewhere in the same file — the Hook Events table lists
UserPromptSubmitas non-blocking, the text under it says "every other event is passive," and "Passive Hooks" says "stdout is ignored."Grepping the shipped docs for
additionalContextreturns exactly two hits, both scoped toStop/SubagentStop.Reproduction (v1.0.5)
Isolated via
GROK_HOME, with aStophook as a positive control so a null result onUserPromptSubmitcan be distinguished from a broken test.$GROK_HOME/hooks/test.json:{ "hooks": { "UserPromptSubmit": [{ "hooks": [{ "type": "command", "command": "/tmp/t/ups.sh", "timeout": 10 }] }], "SessionStart": [{ "hooks": [{ "type": "command", "command": "/tmp/t/ss.sh", "timeout": 10 }] }], "Stop": [{ "hooks": [{ "type": "command", "command": "/tmp/t/stop.sh", "timeout": 30 }] }] } }Each hook logs that it fired and emits a distinct sentinel (
stop.shself-limits to one fire):Run:
Output:
Read as one turn: the model answered
NO-SENTINEL-FOUND(it saw neither theUserPromptSubmitnor theSessionStartsentinel), then theStophook'sadditionalContextreached it and it echoedSENTINEL-STOP-2A8B6F. Positive control passes, so the JSON shape and registration are correct.Both hooks demonstrably ran —
fired.log:Transcript-level confirmation (
sessions/.../updates.jsonl):SENTINEL-UPS-7F3A2B(UserPromptSubmit)SENTINEL-SS-9C4D1E(SessionStart)SENTINEL-STOP-2A8B6F(Stop)The prompt-time sentinels never enter the conversation at all, which rules out "the model saw it and declined to echo it."
Requested behavior
UserPromptSubmit, parse stdout JSON and injecthookSpecificOutput.additionalContextinto the context for that turn — matching Claude Code's documented semantics, sohooks/hooks.jsonfiles port unchanged.UserPromptSubmitto gain deny/gate power. Malformed or absent JSON should stay fail-open and silent.SessionStartadditionalContextwould be a valuable smaller version of the same thing (once-per-session instead of per-turn), if per-turn is too costly.Why it matters
Grok Build already loads Claude-compatible plugins and
hooks/hooks.json. A plugin that ships behavior policy — conventions, review rules, standing instructions — has no way to deliver it on Grok:UserPromptSubmit→ output discarded.PreToolUse→ can rewrite tool input, but only fires when a tool runs, so a tool-free turn gets nothing.Stop→ works, but fires after the work is done and its semantics are "keep the agent working," capped at 8 continuations. Using it as a policy carrier forces an extra model round every turn, after the fact.The only remaining option is a resident rules file (
AGENTS.md/.grok/rules/). That works — verified — but it is strictly worse for this use case: it costs its full token count on every turn regardless of relevance, it can't be updated by shipping a new plugin version, and it can't vary with context. A hook computes the text fresh per prompt and costs nothing when it has nothing to say.The practical effect is that a plugin behaves differently on Grok than on Claude Code silently — the hook is listed in
/hooks, it executes, it exits 0, and its output vanishes. Nothing surfaces to tell the author their policy never arrived.Relation to existing issues
PreToolUse/PostToolUse, for warn-without-deny. It does not coverUserPromptSubmit. A shared implementation would likely satisfy both.Filing here rather than on the harness repo because
xai-org/grok-buildhas issues disabled, and per #77's note this appears to be the intended surface. Happy to move it if there's a better channel.Environment
GROK_HOME, vendor compat scanning disabled ([compat.claude] hooks = false,[compat.cursor] hooks = false) so only the test hooks could fire$GROK_HOME/hooks/, which need no folder-trust grant