gledach / the shelf / snare

snare · slot 07 · shipped

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.

MIT licensed · no account · 0 runtime dependencies

node bin/snare.mjs check real output
ADDED   start SessionStart  home/.claude/settings.json  matcher startup
         node C:/Fixtures/gledach-snare/added-hook.mjs
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 matches
node bin/snare.mjs accept      # remember the current set
node 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.