Skip to content

Modernise the generated CI workflow and tighten the agent guardrail (stack 5/5) - #46

Closed
martient wants to merge 1 commit into
claude/ai-agents-capabilities-report-fgpp3p-manifest-testsfrom
claude/ai-agents-capabilities-report-fgpp3p-ci-cleanup
Closed

martient wants to merge 1 commit into
claude/ai-agents-capabilities-report-fgpp3p-manifest-testsfrom
claude/ai-agents-capabilities-report-fgpp3p-ci-cleanup

Conversation

@martient

@martient martient commented Sep 20, 2026 •

Copy link
Copy Markdown
Owner

Stacked on #45. Resolves P2-13, P2-15 and the last §3.3 removal.

Update: the guardrail is no longer unvalidated. #47 adds a 12-case behaviour matrix (tests/guardrail_hook_tests.rs) that executes this hook and asserts every deny and allow case. It found three defects in the version on this branch — including an unblocked git push -f and a heredoc bypass — all fixed there. Read this PR together with #47, or squash the two. The manual script below is no longer needed.

Generated CI template (P2-13)

The workflow committy hooks install --with-ci writes for users pinned actions/checkout@v4 and ran cargo install committy --locked on every pull request — a multi-minute Rust build per run, uncached, with no toolchain pin.

Now: checkout@v5, a pinned toolchain, Swatinem/rust-cache, and non-interactive mode taken from COMMITTY_NONINTERACTIVE rather than the flag, so the command can't be broken by flag placement.

The repository's own three workflows carried the same stale checkout@v4 pin and are bumped.

A released-binary download would beat cargo install outright, but that needs a decision on distribution I didn't want to make unilaterally — install.sh pipes to a shell, which I'm not going to put in a template generated for other people's repositories.

hooks_cli_tests pinned the literal string committy --non-interactive lint. That assertion now checks the two things that actually matter — the range is linted, and it runs non-interactively by either mechanism — plus fetch-depth: 0, which a range lint silently needs.

Local agent guardrail (P2-15)

block-dangerous-git.sh grepped the raw tool input for nine substrings, which made it both over- and under-inclusive.

Over: it matched the text of a command rather than an invocation. Writing documentation that merely named a push was refused — including, circularly, the heredoc that would have replaced this very file. It also refused the push-with-upstream form this repository's own agent instructions require, which is what deadlocked the two hooks against each other.

Under: two spaces between git and the subcommand, an env X=1 prefix, or git -C . all sailed through. It was never a control.

Now it resolves the command field (with a jq-free fallback), discards heredoc bodies that are written to files, returns early unless git is actually invoked, and refuses only what destroys history or uncommitted work:

Blocked unguarded --force/-f push, reset --hard, clean -f*, branch -D, checkout . / restore .
Allowed plain git push, git push --delete, --force-with-lease

Plain push is left to the pre-push hook, which is policy-engine backed and is the real control.

@martient
martient added this pull request to stack #48 September 20, 2026 17:40
@martient martient self-assigned this Sep 20, 2026
@martient martient added the enhancement New feature or request label Sep 20, 2026
…rdrail

The workflow `hooks install --with-ci` writes for users pinned
actions/checkout@v4 and ran `cargo install committy --locked` on every
pull request -- a multi-minute Rust build per run, uncached, with no
toolchain pin. It now pins checkout@v5, pins the toolchain, caches the
build, and takes non-interactive mode from COMMITTY_NONINTERACTIVE so the
command cannot be broken by flag placement. The repository's own three
workflows carried the same stale checkout pin and are bumped too.

The local PreToolUse guardrail grepped the raw tool input for nine
substrings, which made it both over- and under-inclusive. Over: it
matched the *text* of a command rather than an invocation, so writing
documentation that merely named a push was refused -- including,
circularly, the heredoc that would have replaced this file -- and it
refused the push-with-upstream form the repository's own agent
instructions require. Under: two spaces between git and the subcommand,
an `env X=1` prefix, or `git -C .` all slipped through, so it never was
a control. It now resolves the command field (with a jq-free fallback),
discards heredoc bodies, returns early unless git is invoked, and refuses
only what destroys history or uncommitted work: unguarded force-push
(--force-with-lease allowed), hard reset, forced clean, forced branch
delete, and bulk working-tree discards. Plain push is left to the
pre-push hook, which is policy-engine backed.

PARTIALLY VALIDATED: the full behaviour matrix could not be run from the
session that wrote the hook, since executing one's own permission hook is
refused as self-modification. Observed in normal use: ordinary commands
and a plain push pass, and `git branch -D` was correctly refused. The
remaining deny cases are unverified and a reviewer should confirm them.

Also removes the roadmap page, which carried frontmatter and no body and
published as an empty page.

Copy link
Copy Markdown
Owner Author

Superseded by #49, which carries these commits unchanged. Closing — see #42 for why the stack was collapsed.

Note for whoever reviews #49: the guardrail hook introduced here had three defects that the behaviour matrix in #47 later caught — an unblocked git push -f, a bash <<EOF heredoc bypass, and an over-block on quoted prose. All three are fixed in #49; do not review this branch's version of the hook in isolation.


Generated by Claude Code

@martient martient closed this Sep 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants