Skip to content

feat(hooks): block --no-verify, the one bypass of the four with no rule (v1.41.0) - #60

Merged
lapc506 merged 1 commit into
mainfrom
andres/block-no-verify
Aug 2, 2026
Merged

feat(hooks): block --no-verify, the one bypass of the four with no rule (v1.41.0)#60
lapc506 merged 1 commit into
mainfrom
andres/block-no-verify

Conversation

@lapc506

@lapc506 lapc506 commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

The gap, measured

Non-negotiable 21 names four bypasses. Across all 40 rules in rules.yaml:

bypass rule
push --force yes
--force-with-lease yes
--admin yes
--no-verify none

That asymmetry is worse than a plain gap. Three of the four are covered, so a reader has every reason to assume the fourth is too. The rule reads as enforced and is not.

How it surfaced

On 2026-08-02, an agent resolving a merge conflict ran git commit --no-verify — by reflex, with no justification. It reported the violation itself, discarded the commit with git reset --hard, redid the merge, reapplied the same resolution, and recommitted with hooks enabled.

Nothing stopped it because there was nothing to stop it. It did not route around a guard. It walked through a gap.

The near-miss this rule must not get wrong

git commit -n is --no-verify. git push -n is --dry-run.

Same letter, opposite meaning — which is why the short form is scoped to commit alone. Blocking a dry-run would refuse the safest command in git, and a guard that refuses ordinary work gets bypassed, which is how a guard stops carrying information at all. Pinned by the push-dry-run-allowed test.

No bypass marker, and the message says why

bypass_marker: null, matching the precedent set by test-runtime-guardrail.json (DOJ-6200).

The refusal names the alternative instead of just refusing, and it does so because of what happened in that same incident: the honest attempt failed first, with vitest: command not found, because a fresh worktree had no dependencies. The fix was bun install --frozen-lockfile.

A hook that cannot run is a broken toolchain, not a gate to step over.

A refusal that leaves someone stuck is a refusal that gets worked around next time, so the message hands them the real fix rather than a dead end.

Verification

  • 6/6 rule tests: both spellings on both subcommands, the dry-run near-miss, an ordinary commit, and a quoted mention (documenting the flag performs no skip)
  • Hook suite 346/346, cross-cutting 23/23
  • npx vitest run60/60
  • npm run build-rules regenerates rules.json at 41 rules with no drift

The builder rejected the first draft with rule block-no-verify missing tests (non-empty array required) — its own gate working, and worth noting since this PR is about gates that do not exist.

Version collision, stated up front

Bumps to 1.41.0, assuming #57 (1.39.0) and #58 (1.40.0) land first. All three touch package.json, plugin.json, marketplace.json and CHANGELOG.md; none conflicts with main, and each conflicts with the other two. Whichever order they merge in, the later ones need a renumber — the latent-conflict class #57 exists to surface, now in its third instance.

Created by Claude Code on behalf of @lapc506

…le (v1.41.0)

Non-negotiable 21 names four bypasses: --no-verify, --admin, --force, and
merging past a check. Measured across all 40 rules: --admin, `push --force` and
--force-with-lease each had one. --no-verify had NONE.

That asymmetry is worse than a plain gap. Three of the four are covered, so a
reader has every reason to assume the fourth is too: the rule reads as enforced
and is not.

Found the way these are always found. On 2026-08-02 an agent resolving a merge
conflict ran `git commit --no-verify`, by reflex, with no justification. It
reported itself, discarded the commit, redid the merge and recommitted with
hooks enabled. Nothing stopped it BECAUSE THERE WAS NOTHING TO STOP IT -- it
did not route around a guard, it walked through a gap.

The near-miss the rule must not get wrong, and why the short form is scoped to
one subcommand: `git commit -n` IS --no-verify, while `git push -n` is
--dry-run. Same letter, opposite meaning. Blocking a dry-run would refuse the
safest command in git, and a guard that refuses ordinary work gets bypassed.
Asserted by `push-dry-run-allowed`.

No bypass marker, deliberately, and the message says why rather than asserting
it. That same incident's honest attempt first failed with `vitest: command not
found` because a fresh worktree had no dependencies; the fix was
`bun install --frozen-lockfile`. A hook that cannot run is a broken toolchain,
not a gate to step over -- so the refusal names that command, because a refusal
that leaves you stuck is one that gets worked around next time.

Six tests: both spellings on both subcommands, the dry-run near-miss, an
ordinary commit, and a quoted mention. Hook suite 346/346, vitest 60/60.

Created by Claude Code on behalf of @lapc506

@dojo-code-reviewer dojo-code-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 Review Comments

Comments — 0 blockers, 2 P2. Confidence: 2.60/5.00.

Walkthrough

⚠️ GOVERNANCE WARNING: This PR targets main directly instead of develop. Under standard GitFlow governance, feature development branches should target develop first.

Review Summary

This PR adds the block-no-verify rule to both rules.yaml and its compiled representation rules.json. The rule is designed to block git commit --no-verify, git push --no-verify, and git commit -n to close the last bypass loophole mentioned in Non-negotiable 21 of the dojo-os rules. It also correctly bumps the versioning manifest files (package.json, .claude-plugin/plugin.json, .claude-plugin/marketplace.json) and registers the entry in the CHANGELOG.md.

  • Files/Areas Reviewed: Rules configuration (hooks/rules/rules.yaml, hooks/rules/rules.json), versioning metadata manifests (package.json, .claude-plugin/plugin.json, .claude-plugin/marketplace.json), and the documentation (CHANGELOG.md).
  • Safety Rationale: The changes introduce passive tool-call rules that prevent hook-bypassing without modifying production runtime code, and are backed by robust unit tests.

Verdict

Comment — 0 blockers, 2 P2s.

🟡 P2 — Major

  • hooks/rules/rules.yaml:1318 — 🟡 P2 (major) — False Positive Risk on Commit Messages. The pattern matches ' -n ' or '--no-verify' regardless of whether they are inside a commit message (e.g., git commit -m "feat: support -n in client"). Since 'bypass_marker' is set to null, a developer running a completely valid commit with such a message will be blocked with no way to bypass. Consider either refining the regex to avoid matching inside quoted strings, or introducing a bypass_marker (e.g., 'no-verify-allowed') to let developers unblock themselves in case of false positives.

[pass 1]

  • package.json:3 — 🟡 P2 (major) — Missing Version Update in README.md. The version manifests (package.json, plugin.json, marketplace.json) are bumped to 1.41.0, but README.md is not updated in this PR (still refers to 1.38.0). This violates the repo's own 'warn-version-readme-changelog-sync' rule/guideline. Please update the README.md version header to 1.41.0 to ensure consistent versioning across all surfaces.

[pass 1]


Total findings: 2 compliance (2 total)

Comment thread hooks/rules/rules.yaml
match:
# `git commit -n` IS --no-verify. `git push -n` is --dry-run and must NOT
# match, so the short form is scoped to commit only. Two flags, one letter,
# opposite meanings -- the same near-miss shape the force-push rule guards.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 P2 (major) — False Positive Risk on Commit Messages. The pattern matches ' -n ' or '--no-verify' regardless of whether they are inside a commit message (e.g., git commit -m "feat: support -n in client"). Since 'bypass_marker' is set to null, a developer running a completely valid commit with such a message will be blocked with no way to bypass. Consider either refining the regex to avoid matching inside quoted strings, or introducing a bypass_marker (e.g., 'no-verify-allowed') to let developers unblock themselves in case of false positives.

[pass 1]

Comment thread package.json
{
"name": "@lapc506/make-no-mistakes",
"version": "1.38.0",
"version": "1.41.0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 P2 (major) — Missing Version Update in README.md. The version manifests (package.json, plugin.json, marketplace.json) are bumped to 1.41.0, but README.md is not updated in this PR (still refers to 1.38.0). This violates the repo's own 'warn-version-readme-changelog-sync' rule/guideline. Please update the README.md version header to 1.41.0 to ensure consistent versioning across all surfaces.

[pass 1]

@lapc506
lapc506 merged commit b5fcff1 into main Aug 2, 2026
2 checks passed
@lapc506
lapc506 deleted the andres/block-no-verify branch August 2, 2026 23:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant