A hook that was not there yesterday. On your own machine.
Remembers the hooks a coding agent runs when it starts, and the
editor tasks that run when a folder opens. The first run records
that set. Later runs print nothing when it matches.
A second check against the fixtures in test/fixtures.
One SessionStart command had been added since the
baseline. A check that matches prints nothing and exits 0. This one
exits 1. The path is the fixture, and the line is what the program
printed.
The situation
The next session runs it, and the window looks normal.
A package, a pull request, or a plugin can leave a command in an
agent's settings, or a task that runs when the editor opens the
folder. You have already moved on.
The command is in the settings
Claude Code, Codex and Cursor each keep hooks in a file under your
home directory, and again inside a project. A hook on
SessionStart runs when the agent starts. A hook on any
other event is still a change, and the line says when it would run.
The editor opens the folder
VS Code and Cursor can run a task when a folder opens, from
runOptions.runOn set to folderOpen. On
Windows the command that runs is windows.command when
the task has one. Tasks that entry depends on run too.
What was missing
A baseline of that set, so the change is the thing that speaks.
snare does not edit, disable, or delete a hook.
accept only updates the baseline on this machine.
Running it
There is no npm install, because there is nothing to install.
A fresh clone with Node 22 does everything below. It reads the folder
you name, and it does not open child folders. The baseline goes to
data/, which is gitignored.
git clone https://github.com/gledach/snare && cd snare
node bin/snare.mjs check # quiet when the set matchesnode bin/snare.mjs accept # remember the current setnode bin/snare.mjs doctor # node, and which taught files exist
The command is gledach-snare. npm link puts that
name on your PATH. The copy block above calls the same file and needs no
link. Nothing named snare is installed. That name is an npm
package. gledach-snare check D:\work\repo also reads that
folder. gledach-snare list prints the current set.
gledach-snare report prints data/latest.json.
The commands
Command
What it does
gledach-snare check
Compare the current set with the baseline. The first run records it and says so. A match prints nothing and exits 0. A difference prints ADDED, REMOVED, or CHANGED and exits 1.
gledach-snare accept
Remember the current set. It does not edit, disable, or delete a hook.
gledach-snare list
Print the current set, including when it matches the baseline.
gledach-snare report
Print data/latest.json. Every check refreshes that file.
gledach-snare doctor
Node, the data directory, and which of the taught files exist. It does not print a hook command.
Exit 2 means the command was not recognized, or data/baseline.json
could not be read. A corrupt baseline is left in place. accept
replaces it. There is no scheduled task and no service. Run check
when you want the comparison.
What it reads
The command, hashed. The line, redacted.
The comparison hashes the command, its arguments, a Windows override,
a url, and a prompt. data/ keeps the hash and a shortened
redacted line. It does not keep the raw command.
Every hook event
Claude settings, user and project, including settings.local.json
and the two Windows managed paths when they exist.
SessionStart, Setup and
InstructionsLoaded are marked start.
The status line command and the helper commands are included.
Codex and Cursor
Codex hooks in hooks.json and config.toml,
including commandWindows. notify is marked
auto, because it runs at the end of a turn. Cursor
hooks.json marks sessionStart and
workspaceOpen as start.
One folder, not its children
User tasks, one level of editor profiles, a workspace
.vscode/tasks.json, and a .code-workspace
file sitting in a folder you passed. A hook under
node_modules is missed unless you pass that folder.
What it cannot see
Stated here rather than buried in a wiki.
Every tool on this shelf ships a written audit of its own blind spots. This
is snare's, in full, from its own README.
A hook written and removed between two runs.
Plugin hooks in a cache, and hooks on a skill or a subagent.
They run when that plugin or skill is enabled. snare does not walk those trees.
An MCP server command.
Those processes start with the client too. Claude keeps them in a user file
that also holds account data, so this tool does not open that file.
A PowerShell profile, a startup entry, a service, or a scheduled task.
Whether the hook will actually run.disableAllHooks: true is recorded, and the hooks beside it are
still listed. A folderOpen task can be blocked by workspace
trust or by task.allowAutomaticTasks. A Codex hook can sit in
the file and still not run: some builds want codex_hooks on,
some skip a hook until it is trusted, and older Windows builds did not run
hooks. snare reports the file either way.
A timeout-only edit.
The command is the same, so the run stays quiet.
A binary replaced on disk when the command string is unchanged.
Claude hooks that Cursor imported.
Cursor can run those SessionStart commands. The Claude files
are already in the baseline.
Anything outside the user locations and the folders you named.
Redaction covers sk-ant-, sk-, github_pat_,
ghp_, glpat-, AKIA, xox and
Bearer. It is not a secret scanner. The hash still changes when a
redacted value changes. The printed line stops at 160 characters.
Elsewhere on the shelf
Same author. Different shapes.
Each takes whatever shape suits it, from a single command to a dashboard.
What they share is the promise rather than the plumbing: free, installable,
and honest about what each one cannot see.