Skip to content

Creating Kiro Hooks from Scratch

Hooks are Kiro’s automation engine. They let Kiro automatically perform actions — fix lint errors, run tests, update docs, generate PR descriptions — whenever a specific event happens in your workspace, without you having to type a prompt.

Hooks vs MCP: Hooks automate what Kiro does. MCP controls what Kiro knows. See MCP vs Hooks for a full comparison.


A hook is a YAML file that answers two questions:

  1. When? — Which event should trigger this hook (file saved, test failed, PR opened…)
  2. What? — What should Kiro do when that event fires (a natural-language prompt)

When the event occurs, Kiro reads the prompt, picks up the context from the event (the file path, the test output, the PR diff), and performs the action autonomously.


All hooks live in .kiro/hooks/ at the root of your project:

your-project/
├── .kiro/
│ ├── hooks/
│ │ ├── auto-lint.yaml ← fix lint on save
│ │ ├── update-docs.yaml ← keep docs in sync with code
│ │ ├── run-tests.yaml ← run tests when services change
│ │ └── pr-description.yaml ← auto-generate PR descriptions
│ ├── steering/
│ └── mcp.json
├── src/
└── package.json

The .kiro/ directory should be committed to source control so the whole team shares the same hooks.


If it doesn’t exist yet:

Terminal window
mkdir -p .kiro/hooks

Name the file something descriptive. The name becomes the hook’s identity in Kiro’s UI.

Terminal window
touch .kiro/hooks/auto-lint.yaml

Open the file and write the hook definition:

name: Auto-fix lint errors on save
trigger:
event: file_saved
filePattern: "**/*.{ts,tsx,js,jsx}"
action:
prompt: |
The file {{file}} was just saved.
Run ESLint on it and automatically fix any lint errors.
Do not change any logic — only fix lint rule violations.
If there are no lint errors, do nothing.

Kiro picks up new hooks automatically when you save the YAML file. No restart needed. You’ll see the hook appear in Kiro’s Hooks panel.

For the example above, save any .ts or .tsx file. Kiro should automatically fix any lint errors in that file.


# Required: display name shown in Kiro's Hooks panel
name: <string>
# Required: what fires this hook
trigger:
event: <event-name> # see event list below
filePattern: <glob> # optional — only for file_* events
# Required: what Kiro should do
action:
prompt: |
<natural language instructions for Kiro>
Use {{variable}} placeholders for dynamic values.
# Optional: conditions that must be true for the hook to run
conditions:
- <condition expression>
# Optional: prevent hook from firing more often than this
debounce: <milliseconds>
# Optional: set to false to temporarily disable without deleting the file
enabled: <true|false>

EventFires whenUseful filePattern examples
file_savedA file is saved in the editor**/*.ts, src/**/*.py, **/*.{js,jsx}
file_createdA new file is createdsrc/components/**/*.tsx, **/*.test.ts
file_deletedA file is deleted**/*.md
file_renamedA file is renamed or moved**/*
EventFires when
test_failedAny test in the test suite fails
test_passedAll tests pass after a previously failing run
EventFires when
pull_request_openedA PR or MR is opened
pull_request_updatedNew commits are pushed to an open PR
code_mergedA branch is merged
build_failedA CI build step fails
build_passedA build that was failing now passes
EventFires when
session_startedKiro session opens (project loaded)
terminal_errorA terminal command exits with a non-zero code

Use {{variable}} placeholders in your action.prompt. Kiro substitutes them with real values at runtime.

VariableAvailable inValue
{{file}}file_* eventsFull path of the file that triggered the hook
{{file_name}}file_* eventsFilename only (no path)
{{file_extension}}file_* eventsExtension (e.g. ts, py)
{{changed_files}}Git eventsList of files changed in the commit/PR
{{test_output}}test_failedFull output from the test runner
{{error_message}}terminal_error, build_failedError text from the terminal/build
{{branch_name}}Git eventsCurrent git branch
{{pr_title}}PR eventsTitle of the pull request
{{pr_diff}}PR eventsFull diff of the PR

name: Fix lint errors on save
trigger:
event: file_saved
filePattern: "**/*.{ts,tsx,js,jsx}"
action:
prompt: |
{{file}} was just saved. Run ESLint and fix any violations automatically.
Only fix lint issues — do not change any logic or formatting beyond what ESLint requires.

2 — Keep documentation in sync with code

Section titled “2 — Keep documentation in sync with code”
name: Update JSDoc when function changes
trigger:
event: file_saved
filePattern: "src/**/*.{ts,js}"
action:
prompt: |
The file {{file}} was just saved.
Review all exported functions and classes in the file.
If any have changed signatures or behaviour since their JSDoc comment, update the JSDoc.
If a public function has no JSDoc at all, add one.
Do not modify any implementation code.
Section titled “3 — Run related tests when a service file changes”
name: Run tests when service changes
trigger:
event: file_saved
filePattern: "src/services/**/*.ts"
debounce: 3000
action:
prompt: |
The service file {{file}} was just saved.
Identify the test file(s) that cover this service.
Run those tests. If any fail, show me the failure output and fix the implementation
to make the tests pass — do not modify the tests themselves.
name: Generate PR description on open
trigger:
event: pull_request_opened
action:
prompt: |
A pull request was just opened on branch {{branch_name}}.
Read the diff in {{pr_diff}} and write a professional PR description that includes:
- A one-line summary of what changed
- Why the change was made (infer from the code and branch name)
- A bullet list of specific changes
- How to test this change manually
- Any potential side effects or things reviewers should pay attention to
Post this as the PR body.
name: Auto-fix failing tests
trigger:
event: test_failed
action:
prompt: |
Tests just failed. Here is the output:
{{test_output}}
Analyse the failures. For each failing test:
1. Determine whether the test is correct and the implementation is wrong, or vice versa.
2. If the implementation is wrong, fix it.
3. If the test assertion is outdated due to intentional code changes, update the test.
Do not delete failing tests — fix the underlying issue.
name: Morning standup summary
trigger:
event: session_started
action:
prompt: |
Kiro just started. Run `git log --since="yesterday" --oneline` and summarise
what was committed yesterday. Also list any TODO comments added in the last 24 hours.
Format this as a brief standup-style summary.

7 — Catch secrets accidentally committed

Section titled “7 — Catch secrets accidentally committed”
name: Warn about secrets in new files
trigger:
event: file_created
filePattern: "**/*.{env,json,yaml,yml,ts,js,py}"
action:
prompt: |
A new file was created: {{file}}.
Scan it for accidentally included secrets — API keys, tokens, passwords, connection strings.
Common patterns: strings starting with "sk-", "ghp_", "AKIA", anything with "password" or "secret" as a key.
If you find any, warn me immediately and suggest moving them to environment variables.
If nothing suspicious is found, do nothing.

Conditions let you narrow when a hook fires beyond just the event type:

name: Only lint TypeScript files in src/
trigger:
event: file_saved
filePattern: "**/*.ts"
conditions:
- "{{file}} starts with src/"
action:
prompt: |
Fix lint errors in {{file}}.

Hooks become significantly more powerful when Kiro can also read external context via MCP. The hook triggers the action; MCP provides the data:

name: Verify implementation matches Jira ticket
trigger:
event: pull_request_opened
action:
prompt: |
A PR was opened for branch {{branch_name}}.
Extract the Jira ticket key from the branch name (format: PROJ-123).
If a ticket key is found:
1. Read the Jira ticket using the Jira MCP connection.
2. Compare the acceptance criteria in the ticket against the changes in {{pr_diff}}.
3. List any acceptance criteria that are NOT addressed by the PR changes.
4. Post a comment on the PR with the gap analysis.
If no ticket key is found, skip.

This requires the Atlassian MCP integration to be configured.


Hook not firing?

  • Check the .kiro/hooks/ directory is at your project root (same level as .git/)
  • Verify the YAML is valid — use a YAML linter. A single indentation error disables the whole file.
  • Confirm enabled is not set to false
  • Check that filePattern matches the file you’re saving (test with **/* temporarily)

Hook fires but does nothing useful?

  • Open Kiro’s Hooks panel to see the last run log
  • Make the prompt more specific — “Run ESLint” is better than “check the code”
  • Add context: tell Kiro which tool to run, what to look for, and what not to change

Hook fires too often?

  • Add debounce: 2000 (2 seconds) to avoid re-triggering on every keystroke during rapid saves

Hook interferes with your work?

  • Set enabled: false temporarily or delete the file — hooks are cheap to recreate

DoDon’t
One concern per hook filePut unrelated automations in one file
Be explicit in prompts: name the tool, the scope, the constraintWrite vague prompts like “improve this file”
Add debounce to file_saved hooksLet file_saved hooks fire on every single keystroke
Commit .kiro/hooks/ to gitAdd it to .gitignore
Test with a safe scope first (filePattern)Apply to all files without testing
Say “do nothing if no issues found”Let Kiro make unnecessary edits

Save this as .kiro/hooks/my-hook.yaml and fill in the blanks:

name: <Describe what this hook does in one line>
trigger:
event: <event-name>
filePattern: "**/*.<extension>" # remove if not a file event
debounce: 2000 # remove if not needed
enabled: true
action:
prompt: |
<Tell Kiro exactly what happened: {{file}} was saved / tests failed / a PR was opened.>
<Tell Kiro what to do: run X tool, fix Y issue, generate Z output.>
<Tell Kiro what NOT to do: don't change logic, don't modify tests, only fix lint.>
<Tell Kiro what to do if nothing needs doing: if no issues found, do nothing.>