UserPromptSubmit hooks: two silent failure modes — mid-turn submissions never fire the hook, and additionalContext intermittently never reaches the context
Bug Report: UserPromptSubmit hook intermittently fails to fire (two distinct failure modes)
- Platform: ZCode desktop app (macOS)
- Version: 3.10.1 (build 3.10.1.6272)
- Date: 2026-08-28
- Severity: high (hooks are the user's primary means of steering agent behavior, and both failures are completely silent)
Summary
The UserPromptSubmit hook fails in two independent ways:
- Failure mode A: messages submitted while an agent turn is already running (queued / interrupting messages) do not trigger the hook at all — the hook process is never spawned, so nothing is injected into the model context.
- Failure mode B: the hook does run, but the
additionalContextit returns never reaches the model context. This is intermittent; in this occurrence it lasted about 30 minutes and then recovered on its own.
Both failures are silent: no error, no warning. On top of that, the client log contains zero hook-execution telemetry, so users cannot self-diagnose and maintainers have no signal to localize the failure.
Environment
- App: ZCode desktop 3.10.1 (CFBundleShortVersionString 3.10.1, CFBundleVersion 3.10.1.6272)
- OS: macOS (arm64, Darwin 25.5.0)
- Date: 2026-08-28 (all timestamps below are local time, UTC+8)
- Hook configuration: user-level
~/.zcode/cli/config.json - Session: sess_33f20ebc
Reproduction steps
Prerequisites
- Enable hooks in
~/.zcode/cli/config.json("hooks": { "enabled": true }) and register at least onetype: "command"hook onUserPromptSubmit. - To make evidence collection easy, write the hook as side-effecting and deterministic: on every run the script drops an epoch-named file into a dedicated directory and unconditionally prints a fixed JSON blob, e.g.
{"hookSpecificOutput": {"hookEventName": "UserPromptSubmit", "additionalContext": "..."}}. - No matcher (fires on every message), timeout 10 seconds.
- Run the script manually to confirm it always produces output and has correct permissions.
Failure mode A: messages submitted mid-turn never trigger the hook
- Give the agent a long-running task (e.g. a multi-step edit of a large file) and confirm the turn has started and is still running.
- While the turn is running, type another message into the input box and send it (it gets queued / folded into the running turn).
- Wait for the turn to finish.
- Check all three of the following:
- Hook side-effect directory: there is no execution file timestamped at the moment the mid-turn message was sent → the hook process was never spawned;
- Client log
~/.zcode/cli/log/zcode-YYYY-MM-DD.jsonl: the message has no correspondingturn.startedevent (it was merged into the still-running previous turn); - Raw session transcript
~/.zcode/cli/rollout/model-io-*.jsonl: the user message is followed directly by the assistant reply, with no injected content in between.
- Control group: send a message while the agent is fully idle — all three checks pass (independent turn, execution file present, injection present).
Expected: all three signals present for every message. Actual: for mid-turn messages, all three are missing and the hook process is never spawned.
Failure mode B: hook runs, but additionalContext never reaches the context (intermittent)
- Same prerequisites. In this occurrence the failure started after
config.jsonhad been rewritten wholesale (a third hook added, the file replaced), but causation is unconfirmed. - Send a message while idle and first confirm the hook did run (an execution file exists in the side-effect directory for that moment).
- Inspect the raw session transcript
~/.zcode/cli/rollout/model-io-*.jsonl: the user message is followed directly by the assistant reply — the injection is missing even though the hook ran and returnedadditionalContext. - The loss comes in contiguous stretches (8 consecutive messages over ~30 minutes in this occurrence) and then clears on its own; fully quitting and relaunching the app (~5 restarts) coincided with the recovery.
Note: this mode cannot be reproduced on demand. It is identified and confirmed by the combination "hook execution file exists + injection missing from the transcript".
Evidence (2026-08-28, local time = UTC+8)
Failure mode A (mid-turn submission, hook never ran)
Two messages in session sess_33f20ebc were submitted while a turn was running: local 15:12:54 and 15:19:39 (UTC 07:12:54 and 07:19:39). Three independent sources corroborate each other:
- Client log
~/.zcode/cli/log/zcode-2026-08-28.jsonl: neither message has aturn.startedevent — both were folded into the still-running previous turn. - Hook side effects: hook #1 drops an epoch-named file into
~/.skills-manager/usage-pending/on every execution (filename format like1787164479-45559-30888.json). There are no execution files at those two moments → the hook process was never spawned. - Raw model input
~/.zcode/cli/rollout/model-io-*.jsonl: both user messages are followed directly by assistant replies, with no injected content.
Control group: messages submitted while idle show all three signals (independent turn, execution file, injection).
Failure mode B (hook ran, injection lost)
For the messages at the following 8 timestamps, ~/.skills-manager/usage-pending/ contains execution files (the hook definitely ran), but the raw rollout transcript shows the user message followed directly by the assistant reply — the injection is missing:
14:40:15, 14:45:00, 14:48:22, 14:48:45, 14:50:08, 14:50:28, 14:53:12, 14:53:58
Timeline:
- 14:27:59 —
~/.zcode/cli/config.jsonrewritten wholesale (third hook added, file replaced); - from 14:40 — injections start disappearing in a contiguous window;
- 14:58–14:59 — app fully quit and relaunched 5 times;
- from 15:04 — every message is injected again; stable through 15:30 and beyond (loss window ≈ 30 minutes).
Missing telemetry
~/.zcode/cli/log/zcode-2026-08-28.jsonl contains 7,717 events for the entire day, and zero of them relate to hook execution — no fired / timeout / failed / blocked traces of any kind. Users cannot tell whether a hook ever fired, and maintainers get no signal for localizing failures.
How to re-verify the evidence (anyone can repeat these checks)
# 1. Hook side-effect files: named <epoch seconds>-<pid>-<seq>.json.
# Local 15:12:54 / 15:19:39 (UTC+8) = epoch 1787901174 / 1787901579.
# The second command should print 0 → the hook process was never
# spawned at those moments (failure mode A)
date -r 1787901174 # → Fri Aug 28 15:12:54 CST 2026, confirms the epoch mapping
ls ~/.skills-manager/usage-pending/ | grep -c -e '^1787901174' -e '^1787901579' # → 0
# 2. Client log: timestamps are ISO UTC (local minus 8 hours).
# The session has 12 turn.started events for the whole day; the list below
# contains nothing at 07:12:5x / 07:19:3x → neither mid-turn message
# started a new turn
grep 'sess_33f20ebc' ~/.zcode/cli/log/zcode-2026-08-28.jsonl \
| grep 'turn.started' | grep -o '"timestamp":"[^"]*"'
# 3. Raw session transcript: locate the file by session ID, then check whether
# any injected content appears between the user message and the assistant reply
grep -l 'sess_33f20ebc' ~/.zcode/cli/rollout/model-io-*.jsonl
# 4. Missing telemetry: across the whole day, the only hook-related string in the
# log is the hookCount field in startup events (29 occurrences); there is no
# hook-execution event of any kind
grep -io 'hook[a-z]*' ~/.zcode/cli/log/zcode-2026-08-28.jsonl | sort | uniq -c # → 29 hookCount
grep -c '"event":"hooks\.' ~/.zcode/cli/log/zcode-2026-08-28.jsonl # → 0
Note: the log also contains 78 occurrences of the string timeout, all from MCP connection events (mcp.server.connect.started) and unrelated to hooks; the 29 hook matches are all the hookCount configuration field inside bootstrap.app.startup.plugins.completed startup events. In other words, the client log records nothing at all about hook execution.
Ruling out user misconfiguration
~/.zcode/cli/config.json:hooks.enabled: true; threetype: "command"hooks on UserPromptSubmit, no matchers, timeout 10 seconds.- The hook scripts unconditionally
cata fixed JSON blob (deterministic); they always produce output when run manually, and file permissions are correct. - No workspace-level override configuration.
- The exact same configuration injects successfully on every message after 15:04, which further rules out a configuration problem.
Expected vs. actual behavior
Failure mode A
- Expected: a message submitted mid-turn (queued or interrupting) triggers UserPromptSubmit when it is processed, and its
additionalContextis injected with that message. - Actual: the hook process is never spawned; the injection is silently lost.
Failure mode B
- Expected: when a hook runs and its output contains
additionalContext, that content always reaches the model context. - Actual: intermittently the content never reaches the model context, with no error of any kind.
Impact
These hooks inject the user's collaboration and reply rules (reply format, language style, how tasks are delegated, etc.) into every message; they are the user's primary means of steering agent behavior. Both failures are silent — no error, no warning — so by the time the user notices, the agent has already ignored the rules for multiple turns. Failure mode A additionally means that interrupting a long-running task (a high-frequency action) always loses the injection — precisely the situation where the user needs the rules to apply the most.
Workarounds (for other affected users)
- To make the injection apply: press Esc to interrupt the running turn, then send the message (the standard community workaround for upstream issue #31114).
- When failures come in waves: fully quit and relaunch the ZCode app (here, the recovery at 15:04 coincided with the restarts).
- Verification: while idle, send a message asking the model "is there any injected content starting with ⚠️ right after this message?"
Requested fixes
- Fix both failure modes.
- Add hook-execution telemetry to the client log (at minimum: fired / outcome / duration).
- Document whether queued / interrupting messages trigger UserPromptSubmit.
- Surface a user-visible indication when a hook fails or is skipped.
Related upstream issues (same symptoms in claude-code)
- anthropics/claude-code #31114 — queued messages during generation skip the UserPromptSubmit hook (matches failure mode A)
- anthropics/claude-code #40647 — command-type hooks intermittently skipped
- anthropics/claude-code #19643 — hook executed but additionalContext missing from context (matches failure mode B)