Skip to main content
Hooks let you run custom logic at key points during Agent execution in the Qoder IDE and JetBrains plugin — no source code changes required. Edit a JSON config file to:
  • Block dangerous operations before a tool runs
  • Auto-lint after every file write to enforce code style
  • Send a desktop notification when the Agent finishes, so you don’t have to watch the IDE
Unlike prompt instructions, hooks are deterministic — when the event fires, your script runs. No model interpretation, no drift.

Supported Events

The IDE / JB plugin currently supports five hook events:

Quick Start

Here is an example that blocks rm -rf commands:
1

Create the script

2

Add the config

Add the following to ~/.qoder/settings.json:
3

Verify it works

Open your IDE and ask the Agent to run a command containing rm -rf in the Qoder plugin panel. The hook blocks execution and feeds the error message back to the Agent.

Use Cases

How It Works

The hook lifecycle comes down to three steps: write a script, register it in config, and it takes effect automatically. When the Agent reaches a lifecycle event (such as “before a tool call”), the plugin checks whether any hooks are registered for it:
  1. The plugin loads all hook configurations at startup.
  2. During Agent execution, the plugin encounters a lifecycle event (e.g. PreToolUse).
  3. The plugin iterates through every hook group registered for that event and evaluates the matcher against the current context.
  4. Hooks with a matching matcher run their shell scripts in order.
  5. Each script receives event context as JSON via stdin and returns a decision through its exit code and stdout.
  6. The plugin reads the result and decides what to do next — proceed or block.

Prerequisites

  • jq: The example scripts use jq to parse JSON. Install it with brew install jq on macOS or apt install jq on Linux.
  • Script permissions: Every hook script must be executable (chmod +x).

Creating Hooks

1. Decide What You Need: Pick an Event and a Matcher

Start by deciding where to intervene and what to match:

2. Write the Hook Script

A hook script is a standard shell script that follows this protocol: Input: JSON event context delivered via stdin. Output: Determined by the exit code.
Script template:
You can also output JSON on stdout when exiting with exit 0 for finer-grained control:

3. Register the Script in Your Config

Add the script path under the corresponding event in your settings file:

4. Test and Debug

You can test your scripts directly from the terminal by piping JSON input:
Check the stderr output (the block message):

Configuring Hooks

Config File Locations

Hook configurations are loaded from the following files. When hooks are defined at multiple levels, they are merged and executed together (listed from lowest to highest priority):
The IDE / JB plugin and the CLI share the same config files. Hot reload is not yet supported — restart the IDE after editing hook configurations for changes to take effect.

Config Format

You can define multiple matcher groups under a single event, and each group can contain multiple hook commands.

Matcher Rules

matcher determines when a hook fires. What it matches against depends on the event (see each event’s description).

Tool Name Mapping

Qoder supports two sets of tool names — native names and Claude Code-compatible names. You can use either in your matchers; the plugin maps them internally. For example, matcher: "Bash" is equivalent to matcher: "run_in_terminal".

Writing Hook Scripts

Hook scripts receive JSON input via stdin and communicate results through their exit code and stdout. This section covers the input/output format common to all events. For event-specific fields, see Hook Events.

Input

Your hook script receives JSON data via stdin. Every event includes these common fields: Each event adds its own fields on top of these (see the individual event descriptions). Parse the input with jq:

Output

Hooks communicate results through their exit code and stdout. Exit code determines the basic behavior: stdout JSON (only parsed when exit code is 0) provides fine-grained control for certain events. See each event’s description for the supported fields. When the exit code is non-zero, stdout is ignored.

Environment Variables

When a hook script runs, the plugin injects the following environment variables that your script can reference:

Hook Events

UserPromptSubmit

Fires after the user submits a prompt in the IDE plugin panel, before the Agent begins processing it. Use it for prompt screening, content filtering, or auto-injecting context. Matcher: None. This event fires for all user input. Extra input fields:
Blocking the prompt: Exit with code 2. The stderr content is displayed to the user as an error, and the Agent does not process the prompt. stdout JSON fields (when exit code is 0):
Example: Auto-append project conventions to every prompt

PreToolUse

Fires before a tool executes. Can block tool execution. This is the most commonly used hook event — ideal for blocking dangerous commands, validating file paths, or enforcing permissions. Matcher: Tool name (e.g. Bash, Write, Edit, Read, Glob, Grep, or MCP tool names like mcp__server__tool). Extra input fields:
Blocking tool execution: Exit with code 2. The stderr content is returned to the Agent as an error. See the Quick Start for a full example. stdout JSON fields (when exit code is 0):

PostToolUse

Fires after a tool executes successfully. Not blockable. Use it for auto-linting, logging, or result analysis. Matcher: Tool name. Extra input fields:
stdout JSON fields (when exit code is 0):

PostToolUseFailure

Fires when a tool call fails. Not blockable. Use it for error monitoring, retry suggestions, or logging. Matcher: Tool name. Extra input fields:

Stop

Fires after the Agent completes its response (i.e. the Agent has no more tool calls to make). Can block the Agent from stopping. Use it for quality gates, desktop notifications, logging, task status reports, or Harness self-evolution. Matcher: None. This event fires whenever the Agent stops. Extra input fields:
Blocking the Agent from stopping: Exit with code 2. The block reason is injected into the conversation as a user message, and the Agent continues working. stdout JSON fields (when exit code is 0):
Preventing infinite loops: When a Stop hook blocks the Agent (exit 2), the Agent retries and the Stop event fires again with stop_hook_active: true. Your script must check this field and exit 0 when it is true, otherwise the hook will block indefinitely.

Work with Hooks

Block Dangerous Commands

Check for destructive operations like rm -rf or DROP TABLE before the Agent runs a shell command. Script ~/.qoder/hooks/block-dangerous.sh:
Config: event PreToolUse, matcher Bash , command ~/.qoder/hooks/block-dangerous.sh.

Auto-Lint After File Writes

Automatically run a linter every time the Agent writes or edits a file. Script ${project}/.qoder/hooks/auto-lint.sh:
Config: event PostToolUse, matcher Write|Edit, command .qoder/hooks/auto-lint.sh.

Log Tool Failures

Write to a log file whenever one of the Agent’s tool calls fails, making it easier to troubleshoot. Script ~/.qoder/hooks/log-failure.sh:
Config: event PostToolUseFailure, no matcher (matches all tools), command ~/.qoder/hooks/log-failure.sh.

Desktop Notification on Completion

Show a system notification when the Agent finishes a task. Great for long-running tasks. Script ~/.qoder/hooks/notify-done.sh (macOS):
Config: event Stop, no matcher, command ~/.qoder/hooks/notify-done.sh.

Prompt Content Screening

Screen user prompts for sensitive data (passwords, keys, etc.) before submission to prevent accidental leaks. Script ~/.qoder/hooks/check-prompt.sh:
Config: event UserPromptSubmit, no matcher, command ~/.qoder/hooks/check-prompt.sh.

Full Config Example

Here is a complete configuration with hooks for all five events:

Things to Keep in Mind

  • Timeout handling: Hook scripts have a default timeout of 30 seconds. If a script times out, it is killed and treated as an allow (proceed). Configurable timeouts are coming in a future release.
  • Error handling: If a script exits with an unexpected code (anything other than 0 or 2), the error message is shown to the user but the Agent continues without interruption.
  • Script permissions: Make sure your scripts are executable (chmod +x).
  • Config merging: When the same event has hooks defined at multiple config levels, they execute in order from lowest to highest priority. If any hook blocks (exit 2), the remaining hooks for that event are skipped.
  • jq dependency: The example scripts rely on jq to parse JSON. Make sure it is installed on your system (brew install jq on macOS, apt install jq on Linux).

Best Practice Scenarios

Who Should Use Hooks

Hook value depends on your role. Here’s a quick mapping:

For Individual Developers

For Teams / Enterprises

Individual scenarios are typically configured in ~/.qoder/settings.json (user-level) or .qoder/settings.local.json (project-level local). Team scenarios should go in .qoder/settings.json (project-level) and be committed to Git to ensure uniform enforcement.

Scenario 1: Prompt Enhancement — Auto-Inject Skills

Pain point: You have to manually specify a Skill every time, or forget to load project-specific context.
Solution: Use a UserPromptSubmit hook to auto-inject a prompt hint guiding the Agent to use a specific Skill.
Config:
Script .qoder/hooks/inject-skill-hint.sh:
Key points:
  • additionalContext is appended to the user prompt as a system-reminder injected into the Agent
  • Uses session_id + temp files for session-level deduplication to avoid injecting the same hint every turn
  • The script can read project config files for project-specific Skill recommendations
  • exit 0 allows the prompt through; exit 2 blocks it (e.g. for non-compliant prompts)

Scenario 2: Sensitive Prompt Blocking

Pain point: Users may accidentally include passwords, keys, internal IPs, or personal data in their prompts, risking information leakage.
Solution: Use a UserPromptSubmit hook to detect sensitive content and block the prompt.
Config:
Script .qoder/hooks/block-sensitive-prompt.sh:
Key points:
  • UserPromptSubmit is a blockable event; exit 2 directly prevents the prompt from reaching the Agent
  • Sensitive patterns support regular expressions for flexible matching (e.g. AWS AKIA prefix, internal IP ranges)
  • Consider extracting patterns to a separate config file (e.g. .qoder/hooks/sensitive-patterns.txt) for team-wide maintenance
  • For more precise detection, call external tools like gitleaks or trufflehog
Difference from Scenario 1: Scenario 1 uses exit 0 + additionalContext for prompt enhancement (injecting context), while this scenario uses exit 2 for prompt blocking (rejecting non-compliant input). Both can coexist under the same UserPromptSubmit event and execute in config order.

Scenario 3: Rule/Skill Usage Analytics

Pain point: Many Rules and Skills are configured but actual usage rates are unknown.
Solution: Use the Transcript system + Stop hook to automatically analyze and record Rule/Skill trigger data after each conversation.
Config:
Script .qoder/hooks/analyze-rule-skill-usage.sh:
Transcript auto-recorded metadata includes:
  • session_meta(rules): All Rules loaded in this session (name, trigger type, file path)
  • session_meta(slash_command): Skills used in this session (name, type, file path)
  • session_meta(session_info): Session mode (agent/plan) and type
Advanced usage: Write a periodic summary script to read ~/.qoder/stats/ JSONL files and generate Rule/Skill usage reports.

Scenario 4: File Edit Tracking

Pain point: Unclear which files the Agent modified in a session and how many times.
Solution: Use a PostToolUse hook matching file-edit tools to log every file change in real time.
Config:
Script .qoder/hooks/track-file-changes.sh:
Advanced usage:
  • Use additionalContext to feed change statistics back to the Agent (e.g. “15 files modified in this session”)
  • Integrate with your team’s code analytics system to track AI-assisted change volume

Scenario 5: Global Usage Analytics

Pain point: No quantitative data on overall Agent usage — can’t assess AI-assisted coding efficiency and quality. This includes: what questions users ask, what text the model returns, which tools are called and their results.
Solution: Use a multi-event hook combo to build full-pipeline usage data collection. UserPromptSubmit captures user questions, PostToolUse captures tool call results, Stop analyzes the complete session summary via Transcript (model replies, tool call distribution, etc.).
Config:
Script ~/.qoder/hooks/usage-tracker.sh:
Data collection dimensions:
Why analyze via Transcript in Stop? UserPromptSubmit and PostToolUse only capture data from the current interaction, while the Transcript file at Stop time contains the complete session history — enabling extraction of model reply text, tool call distribution, and success/failure rates in one pass. See Transcript File Format for details.

Scenario 6: Safety Controls — Dangerous Command Blocking

Pain point: The Agent may execute rm -rf, git push --force, or other dangerous commands.
Solution: Use a PreToolUse hook matching Bash|run_in_terminal to intercept dangerous commands before execution.
Config:
Script .qoder/hooks/block-dangerous-commands.sh:
Key points:
  • exit 2 immediately blocks execution; stderr content is fed back to the Agent as the block reason
  • The Agent will attempt an alternative (e.g. a safer command) after being blocked
  • For richer feedback, return JSON on stdout:

Scenario 7: Quality Gate Before Agent Completion

Pain point: The Agent claims the task is done, but tests are failing or issues remain.
Solution: Use a Stop hook (blockable) to run quality checks before the Agent finishes; block and force the Agent to keep working if checks fail.
Config:
Script .qoder/hooks/quality-gate.sh:
Key points:
  • stop_hook_active is used to prevent infinite loops (it is true when the Agent retries after being blocked)
  • After a Stop hook blocks, the block reason is injected as a user message and the Agent continues working
  • Set a longer timeout (e.g. 120 seconds) since builds and tests may take time

Scenario 8: Harness Self-Evolution — Automated Knowledge Sedimentation

Pain point: Lessons and decisions from each task are scattered in conversation history with no automated sedimentation process.
Solution: Use a Stop hook to automatically trigger a Harness self-evolution flow, analyzing whether the conversation produced reusable lessons and driving the asset lifecycle.
Config:
Script .qoder/hooks/harness-evolution.sh:
Key points:
  • Preventing infinite loops (mandatory): Stop hook scripts must check stop_hook_active. When the Agent retries after being blocked by a Stop hook, this field is true — exit 0 immediately to avoid an infinite loop
  • Use exit 2 + decision:"block" to block the Agent from completing and force a retrospective
  • pending-review.jsonl logs sessions for later batch review
  • Can be combined with a /retro Skill (custom retrospective skill) to form an auto-detect → remind → sediment closed loop
Current implementation options:
Hooks currently only support command type handlers (executing external scripts), so Harness self-evolution has two implementation paths: (A) call an external analysis service, or (B) block the Agent and trigger a retrospective Skill.
Future direction:
The hook system plans to support prompt type and agent type handlers. Once available, Harness self-evolution will upgrade from “script-driven” to “Agent-driven”, enabling true end-to-end automated knowledge sedimentation.

Transcript File Format

The Transcript is a session log file automatically generated by Qoder, located at the path pointed to by transcript_path (e.g. ~/.qoder/projects/<project>/transcript/<session-id>.jsonl). Each line is an independent JSON object appended in chronological order, recording the complete session interaction.

Common Fields Per Line

Record Types

1. session_meta — Session metadata (first line) The first line of every Transcript file records the session’s basic information:
  • data.content.mode: Session mode (agent / plan / ask / debug)
  • data.content.session_type: Session type (assistant / inline_chat, etc.)
2. user — User messages User messages come in two forms: User question (message.content is a string):
Tool result (message.content is an array containing tool_result):
  • is_error: Whether the tool execution failed
  • toolUseResult: Shortcut field for the tool result (same as content[0].content)
3. assistant — Model replies Model reply message.content is always an array containing two element types: Text reply (type: "text"):
Tool call (type: "tool_use"):
  • name: Tool name (e.g. read_file, search_replace, run_in_terminal, Skill)
  • input: Tool call parameters
  • id: Corresponds to tool_use_id in the subsequent tool_result
4. progress — Hook trigger records Records when hook scripts fire and which commands run:

Session Timeline Example

A typical session’s Transcript record order:

Common jq Extraction Commands


Design Principles

Debugging Guide

Step 1: Confirm the Hook Fires

When a hook does not fire as expected, add debug logging at the very start of the script:
Then trigger an Agent operation and check the log:
Troubleshooting: If no output appears, the hook is not firing. Check your config file location, event name, matcher pattern, and script path.

Step 2: Reproduce Locally with Real Input

Use the JSON captured in Step 1 to test the script outside of Qoder:
Tip: Save common test cases as files for repeated validation.

Step 3: Other Debugging Methods

  1. Check the Transcript: ~/.qoder/projects/<encoded-path>/transcript/<session>.jsonl — parse with jq line by line
  2. Check Hook logs: Search for [hook] prefix in Qoder logs for execution results and timing
  3. Start simple: First verify the hook fires with a bare exit 0, then add business logic incrementally
  4. Clean up: Remove debug code (/tmp/hook-debug.log writes) after debugging to avoid performance impact

Quick Start Template

Minimal Config

Save the following to ~/.qoder/settings.json (user-level) or <project>/.qoder/settings.json (project-level):

Project Directory Structure