Part 17. Qwen Code Hooks: Workflow Automation
Qwen Code hooks are scripts that run at predefined moments in a session: at startup, after a user request, before using a tool, after using a tool, on tool error, or before completing a response.
In SDD, hooks are not meant to replace specifications. They are meant to make the process less dependent on human memory: remind the agent about rules, record important events, stop dangerous commands, add short context, or collect traces for subsequent verification.
Basic schema:
flowchart TD
A["Qwen Code Event"] --> B["Matcher Filter"]
B --> C["Hook<br/>command or http"]
C --> D{"Can continue?"}
D -- "yes" --> E["Qwen Code continues working"]
D -- "no" --> F["Qwen Code shows reason for stopping"]
C --> G["Additional Context"]
G --> EWhen hooks are appropriate
A hook is appropriate if an action should happen regularly and consistently:
- record the fact of tool usage;
- add a short SDD reminder after
/clear; - check a potentially dangerous command before execution;
- save a tool error for retrospective analysis;
- send an event to an internal log;
- add a link to the current specification to the context;
- run a lightweight background check after a file change.
A hook is not needed if a rule is sufficient to write in QWEN.md. For example, "read specifications first" is better left as a behavior rule. But "every Bash execution with a dangerous command must be stopped" is already suitable for a hook.
Events most commonly needed
For the learning process, it is enough to understand a few events.
| Event | When it triggers | Typical meaning in SDD |
|---|---|---|
SessionStart | at startup or session resumption | add a short project reminder |
UserPromptSubmit | after a user request | check the request and add relevant context |
PreToolUse | before using a tool | stop a dangerous action |
PostToolUse | after successful tool use | record the fact, run a lightweight background check |
PostToolUseFailure | after a tool error | save the error for analysis |
Stop | before the agent completes its response | record session summary or remind about verification |
| PreCompact | before context compression | save important conclusions before details are lost |
Do not connect everything at once. Start with one or two events, otherwise the process will become opaque.
Hook types
Qwen Code supports several executor types.
command runs a local command. This is the main option for project hooks: a Python script, shell script, or small utility.
http sends an event to an HTTP address. This is convenient for centralized logging or an internal audit system, but requires careful setup of secrets and allowed addresses.
function is intended for internal JavaScript functions at the session level. For a typical project, command or http is almost always sufficient.
What a hook receives
A command hook receives JSON via standard input. It contains common fields: session identifier, current directory, event name, event time. For tool events, the tool name, tool input, result or error are added.
A hook can return JSON via standard output. Most often it does one of three things:
- changes nothing and simply exits;
- adds
additionalContextso that Qwen Code sees a short message in the next step; - prohibits an action before tool use and explains the reason.
The exit code is also important. 0 means the hook worked successfully. 2 is used for a blocking error. Other codes are considered non-blocking errors: the main Qwen Code flow continues, and details are visible in debugging.
Minimum file set
Examples have been moved from the lesson into separate files:
- examples/hooks/settings-workflow.example.json — example of hook configuration;
- examples/hooks/pre_tool_guard.py — protective hook before dangerous commands;
- examples/hooks/log_tool_result.py — logging successful and unsuccessful tool calls;
- examples/hooks/inject_sdd_context.py — adding short SDD context;
- examples/hooks/README.md — brief description of examples.
Configuration in the learning project:
mkdir -p .qwen/hooks
cp sdd-qwen-code-ru/examples/hooks/pre_tool_guard.py .qwen/hooks/pre_tool_guard.py
cp sdd-qwen-code-ru/examples/hooks/log_tool_result.py .qwen/hooks/log_tool_result.py
cp sdd-qwen-code-ru/examples/hooks/inject_sdd_context.py .qwen/hooks/inject_sdd_context.py
chmod +x .qwen/hooks/*.py
Copy the settings example separately and merge it with the existing .qwen/settings.json manually:
cp sdd-qwen-code-ru/examples/hooks/settings-workflow.example.json .qwen/settings-hooks.example.json
Do not overwrite working settings for the model, authentication, and MCP servers.
Protective hook before a tool
PreToolUse is suitable for actions that must be checked before execution. The most obvious example is shell commands.
The file examples/hooks/pre_tool_guard.py checks Bash input against several dangerous patterns: deleting root or home directory, git reset --hard, git clean -fd, running a downloaded script via shell. This is not a full sandbox. It is the last check before an obviously risky action.
Important: a protective hook must explain the reason for stopping. If the agent sees only "command forbidden," it will start guessing workarounds. If it sees a specific reason, it can suggest a safe alternative.
Tool logging
PostToolUse and PostToolUseFailure are suitable for event logging. In SDD, such a log is useful not by itself, but as a source for retrospective analysis:
- which commands often failed;
- which files the agent changed before an error;
- which checks were run manually;
- which actions should be moved to
validation.md; - which rules should be recorded in
QWEN.md.
The file examples/hooks/log_tool_result.py writes compact records to .qwen/hooks/logs/tool-events.jsonl. This is a local technical log. Do not store secrets there, large model responses, or full environment dumps.
For logging, asynchronous mode is usually enabled so that the main workflow does not wait for the log write.
Adding SDD context
SessionStart and UserPromptSubmit are suitable for short context additions. This is especially useful after /clear: the chat history is cleared, but the project process should remain visible.
The file examples/hooks/inject_sdd_context.py does not read the entire project. It checks for the presence of QWEN.md, specs/mission.md, specs/tech-stack.md, and specs/roadmap.md, then adds a short reminder. Such a hook should not turn into a hidden specification. It only directs the agent to the files where the source of truth lives.
Hooks and checks
There is a temptation to run npm test after every file change. Usually this is a bad idea: the agent will become slow, and errors will arrive at inappropriate moments. It is better to separate checks:
- lightweight checks can be run automatically and asynchronously;
- heavy checks are run by an explicit step from
validation.md; - blocking hooks are used only for dangerous actions;
- results of automatic checks should go into a report or log, not get lost in noise.
If a hook runs a check, it should answer one question. For example: "did the fast type check fail after a TypeScript file change?" Do not turn one hook into a full branch merge process.
Security rules
Hooks run in your environment with your privileges. Therefore, treat them like ordinary automation code.
Minimum rules:
- store project hooks in the repository and review them as code;
- set
timeoutso that a stuck hook does not block the session; - use asynchronous mode for logs and background checks;
- do not pass secrets via command line;
- for HTTP hooks, set a list of allowed environment variables;
- do not send sources and prompts to external services without explicit team decision;
- do not write hooks that silently change project files;
- add a clear message when blocking an action.
Project hooks should be enabled only in a trusted directory. If you opened someone else's repository, first read .qwen/settings.json and .qwen/hooks/, and only then allow automatic execution.
What not to automate
Bad ideas:
- automatically fix code after every tool error;
- block any commands not in a precompiled list;
- add large specification fragments to the context on every request;
- record full model responses in a permanent log;
- mix memory, checking, formatting, and security in one script;
- use hooks as a hidden replacement for
requirements.md,plan.md, andvalidation.md.
A hook should be small and understandable. If you cannot explain its purpose in one phrase, it is most likely doing too much.
Practical exercise
- Copy the hook examples into your learning project.
- Connect
pre_tool_guard.pyonly toPreToolUseforBash. - Connect
log_tool_result.pytoPostToolUseandPostToolUseFailure. - Connect
inject_sdd_context.pytoSessionStartandUserPromptSubmit. - Launch Qwen Code in the project and execute
/clear. - Ask the agent to read the roadmap and suggest the next feature without changing files.
- Verify that the log appeared in
.qwen/hooks/logs/tool-events.jsonl. - Try a dangerous command in a safe test form and make sure the protective hook explains the block.
After the exercise, answer in writing:
- which hook actually helped the process;
- which hook turned out to be noisy;
- what is better to move to
QWEN.md; - what is better to leave as a manual step in
validation.md.
Connection to following parts
In the next part, hooks are considered from a security perspective: they can protect the process, but they themselves require review. After that, in the part about SQLite memory, the same mechanism is used for collecting events, background summarization, and adding short memory to new sessions.