cursor-hooks-valid¶
Cursor hooks must use known events and valid commands; project hooks require version 1
| Severity | error (auto) |
| Autofix | - |
| Since | v0.19.0 |
| Repo Types | cursor, cursor-marketplace, cursor-plugin |
| Category | Cursor |
Why¶
.cursor/hooks.json runs shell commands around the agent loop — before a
shell command executes, before an MCP tool call, after a file edit. It ships
in the repository, so anyone who can land a commit can add one.
A key Cursor does not dispatch is ignored: the file loads, the hook never
fires, and nothing is reported. A hook meant to block dangerous shell
commands that is spelled beforeShellExec is not a hook at all, and the
failure looks identical to a hook that simply never triggered.
Cursor's event set grows — the 1.7 launch shipped six, and there are now
over twenty across agent, Tab, and application lifecycle. An unrecognised
name is therefore reported at warning, not error, and extra-events
lets a project accept an event newer than its skillsaw without waiting for
a release.
A hook entry is a command hook by default. A type: "prompt" hook asks the
model a question instead of spawning a process, and carries its text in
prompt rather than command.
This rule checks the shape. The commands themselves are scanned by
hooks-dangerous and, when you want every hook
reviewed rather than only the risky-looking ones,
hooks-prohibited — both read Cursor hooks through
the same path they use for Claude Code hooks and settings.
Severity¶
Structural defects that stop a hook running are errors: a missing project-hook version or
a non-integer version, a missing hooks object, an event whose value is not
an array, an entry that is not an object, an unknown type, a missing or
empty command/prompt, a non-string matcher, and a timeout that is
not a finite number.
A bad matcher is worth the error even though the hook still runs: skillsaw
falls back to the .* wildcard so the security rules keep seeing the
command, which means the hook fires on everything and nothing else would
tell you.
Three checks are warnings, because the file still loads and the rest of it
still runs: an unrecognised event name, an empty hooks object, and an
event whose array is empty and so configures nothing. A severity configured
for the rule applies to these as well.
Examples¶
Bad — a typo'd event that never fires, and a hook with nothing to run:
{
"version": 1,
"hooks": {
"beforeShellExec": [{ "command": "./scripts/audit.sh" }],
"afterFileEdit": [{ "command": "" }]
}
}
Good — a command hook and a prompt hook:
{
"version": 1,
"hooks": {
"beforeShellExecution": [
{ "command": "./scripts/audit-shell.sh" },
{ "type": "prompt", "prompt": "Does this command look safe?", "timeout": 10 }
],
"afterFileEdit": [{ "command": "./scripts/format.sh" }]
}
}
How to fix¶
- Correct the event name to one Cursor dispatches. If Cursor added it after
this skillsaw release, list it under the rule's
extra-eventssetting:
- Give every command hook a non-empty
command: an absolute path, a path relative to the project root, or a shell snippet. For a script stored beside the manifest, use.cursor/hooks/script.sh, not./hooks/script.sh. Give every prompt hook a non-emptyprompt. - Set
"version": 1in project hooks — it is required there. Plugin hooks may omit it; when present,1is the only accepted value. Write it unquoted;"1"is a string.
Plugin hooks¶
Native Cursor plugins also load hooks from hooks/hooks.json, a declared
file, or an inline hooks object in their manifest or marketplace entry.
The same event and handler checks apply. Unlike project .cursor/hooks.json,
plugin hooks may omit version; an explicit version is still validated.
A plugin that also ships a Claude Code manifest often keeps Claude's
hooks/hooks.json at that default path, with matcher groups that nest a
hooks array under PascalCase events. When every entry has that shape, the
rule reports one finding for the file instead of checking each entry. Declare
hooks in the Cursor manifest or marketplace entry, pointing at a separate
Cursor-format file such as hooks/hooks-cursor.json.
That finding is a warning when no manifest or marketplace entry declares
hooks and Cursor only picks the file up at its default path. When the
author pointed Cursor at the Claude-format file, or wrote Claude-format hooks
inline, it keeps the rule's error severity. A severity set in .skillsaw.yaml
overrides both.
Configuration¶
| Parameter | Description | Default |
|---|---|---|
extra-events |
Additional hook event names to accept, for events newer than this skillsaw release | [] |
Run skillsaw explain cursor-hooks-valid to see this documentation and the rule's effective configuration in your terminal.