amont-agent 2.1.0

A guard that inspects a shell command before Claude Code runs it
amont-agent-2.1.0 is not a library.

amont-agent

Your agent just ran git push … | tail -5, the push was rejected, and the command reported success.

A pipeline's exit status is its last command's. tail succeeds at tailing an error message — so a rejected push, a push killed by a timeout, and a push that never left the machine all look identical, and the trimming throws the error text away too. The failure is silent in both channels, and the model reads its own impatience as a green tick.

No git hook can catch that. The mistake is in the command string and never reaches one.

amont-agent is a Claude Code PreToolUse hook that reads a shell command before it runs, and can observe it, advise against it, or refuse it.

CI License

$ amont-agent check 'git push origin main 2>&1 | tail -5'
pipe-to-tail [deny]
  `git push` pipes into `tail`, so the pipeline reports tail's exit status,
  not git push's. A failed, rejected or timed-out run reads as success, and
  the trimming discards the error text as well.
  → Run `git push` on its own and read its output afterwards. Then verify the
    effect rather than the exit code.
  ▸ git push origin main 2>&1 | tail -5

Install

curl -fsSL https://raw.githubusercontent.com/fredericrous/amont-agent/main/install/install.sh | sh

Then wire it in — a separate, deliberate step, because a program that can refuse your agent's commands should not add itself to your settings as a side effect of you downloading it:

amont-agent install          # prints the settings block, writes nothing
amont-agent install --write  # merges it into ~/.claude/settings.json
amont-agent doctor           # installed, runnable, and actually firing?

Also: brew install fredericrous/tap/amont-agent · cargo install amont-agent · prebuilt binaries for Linux (gnu/musl, x86_64 and aarch64), macOS (Intel and Apple silicon) and Windows.

It measures before it blocks

Five of the six rules ship as observe — they record and say nothing at all. That is not timidity, it is the method:

stance effect
observe records the firing and says nothing
advise puts the reason into the model's context; refuses nothing
deny refuses the tool call, with the reason and the remedy

observe and advise are not two ways of saying "not blocking yet". additionalContext enters the model's context and changes its behaviour, which contaminates the rate the observation exists to measure. A rule that talks is intervening.

So a rule is promoted from your own transcripts, not from an argument:

amont-agent backtest --since 2026-07-06         # firings per 1,000 tool calls, weekly
amont-agent explain pipe-to-tail --format cases >> tests/corpus/pipe-to-tail.cases
$EDITOR tests/corpus/pipe-to-tail.cases          # each `?` becomes match or nomatch
amont-agent corpus check                         # and this runs in the test suite
amont-agent graduate pipe-to-tail --to deny

pipe-to-tail is the only rule that blocks, and it blocks because seven consecutive weeks of measurement showed no downward trend while every other habit halved. A habit the model is already correcting does not need a deny.

Demotion is not gated at all — amont-agent demote <rule>, no questions. A guard that is hard to back out of is one people uninstall instead of demoting, and uninstalling takes every rule with it.

The full method.

The rules

rule ships as what it catches
pipe-to-tail deny a mutating command whose status is swallowed by a pipe
bare-stash-pop observe git stash pop with no ref, where refs/stash is shared across worktrees
gh-pr-merge-auto observe --auto on a repo with no required checks, which merges immediately
no-verify observe turning the whole commit gate off rather than one check
git-add-broad observe staging the tree instead of the change
stale-base advise a branch or worktree started from a checkout the remote has moved past
push-preflight advise a git push whose slow pre-push test gate has not been rehearsed with amont run pre-push

stale-base advises from the start because it refuses nothing and names a failure no correcting loop can see: nothing fails when you build on stale code. The work is correct against the code it can see, and the conflict arrives later, from somewhere else. So a session opening in a checkout the remote has moved past is told — after a five-second, one-branch fetch that never pulls. Why it never pulls.

push-preflight advises for the same reason. git opens its connection to the remote before it runs pre-push and holds it idle for as long as the test gate takes; a remote that closes idle sessions kills the push after the gate has already passed, and the model reads "the network" where the cause was the gate's placement. With amont ≥ 1.27, amont run pre-push runs the same gate with no connection open and stamps the tree, so the push that follows skips the suite. The rule speaks only when confirm finds all three facts: amont guards this repository's pushes, a test gate would run for this push, and HEAD's tree carries no stamp yet.

What it will not do

  • It never emits allow. Approving everything it has no objection to would switch off the permission system it was installed beside. Silence is how it says "no objection".
  • Every failure path is silence. An unreadable payload, an unknown event, a command it cannot parse, a rule that panics — all exit 0 having written nothing. A hook that fails toward refusing gets deleted from settings.json, which switches off every rule at once; one that fails toward silence loses a single firing.
  • It does not judge what it cannot read. Heredocs without terminators, eval, sh -c, unbalanced quotes — opaque never fires.
  • It does not phone home. No telemetry, no update checks. Every firing is journalled to ~/.claude/amont-agent/journal.log, redacted, and it only counts — nothing in it may participate in a decision.
  • A cloned repository cannot change a stance. Promotion power lives in your own git config, never in a committed file.

The reasoning in full.

Turning it down

git config --global amont.agent.pipe-to-tail.stance observe   # one rule
git config --global amont.agent.stance observe                # all of them
AMONT_AGENT_OFF=1                                             # this shell
amont-agent uninstall --write                                 # remove the entries

Documentation

Installing · Stances · The rules · Measuring and graduating · The session notice · Configuration · What it will not do

Contributing

make check    # fmt, clippy -D warnings, tests — exactly what CI runs

One crate, serde and serde_json its only dependencies, and both only for reading — Claude Code's payload and your settings.json, two shapes defined by somebody else. What this binary writes is emitted by a hand-rolled escaper, so the reading and the writing share no representation.

Related

amont — git hooks that catch a bad commit before it exists, by the same author. Independent of this: no shared code, and neither needs the other. They meet in one optional place, described in the session notice.

attest — the CI end of the same story: amont signs a note at pre-push naming the gates that really ran, and attest verifies it so CI can skip them. No connection to this guard beyond the author and the conviction all three share — trust what was verified, never what was reported. The masked push at the top of this page is what that looks like when it fails.

License

MIT.