git commit --no-verify skips pre-commit and commit-msg, but not prepare-commit-msg. The gate sat on that stage, so the pre-commit framework ran on every commit regardless and did its stash-unstaged dance. Inside the agent sandbox its rollback (git checkout -- .) cannot unlink the bind-mounted .claude/settings.json, aborts mid-restore, and leaves every unstaged change reverted to HEAD -- twelve files of work, no commit created.
The gate itself is harmless: merge_signed.sh returns 0 immediately without a MERGE_HEAD. Only the stage was wrong. On commit-msg it still runs for a normal merge commit, and --no-verify now behaves as documented. Tradeoff: a merge committed with --no-verify skips the signature check, same as it already skips make test.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
git writes MERGE_HEAD only after the pre-merge-commit hook returns, so the
gate never found it and aborted every non-fast-forward merge with "No merge
in progress"; git then wrote the merge state via abort_commit(), leaving a
half-finished merge that git status reported as "still merging". The gate
had therefore never verified a single signature.
git also skips pre-merge-commit entirely once a merge conflicts, so the
conflict-resolution commit was unguarded as well. prepare-commit-msg fires
with MERGE_HEAD present on both paths and no-ops on ordinary commits.
Verified in a scratch repo with a real pre-commit install: ordinary commit
passes silently, clean automerge and conflict-resolution commit both block
an unsigned incoming commit, and the extra stash/restore cycle round-trips
unstaged changes byte-identically.
pre_commit install never removes hook types dropped from the config, so the
install script now also uninstalls the stale pre-merge-commit hook.
Switching branches leaves __pycache__ directories behind after their source files vanish; the now-orphan parent dirs (no source __init__.py, only ignored bytecode) trip the init-files lint. Wire the existing 'make clean-pycache-dirs' cleanup into a post-checkout hook and add the stage to default_install_hook_types so 'pre-commit install' picks it up. Non-privileged: it removes host-owned orphans and warns-and-continues on container-owned ones.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The new dir handlers pulled utils.cache.yaml into the env build, which
bootstrap runs (make install-system-python) before any Python dependency
exists; on arch and manjaro, whose images ship no preinstalled PyYAML, the
.env generation died with ModuleNotFoundError and broke PKGMGR and the dev
environment jobs. Read the plain KEY: "value" entries with a stdlib line
parser instead; Jinja values and missing keys stay hard errors.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A merge commit bypassed every signature check: test-signed only inspects
HEAD and the pre-commit hook does not run for merge commits. Add
scripts/git/assert_merge_signed.sh, which fails when any commit the merge
brings in (HEAD..MERGE_HEAD) reports %G? status N, wire it up as
make test-merge-signed, and register it as a pre-merge-commit hook so
pre-commit install now also installs that hook type.
Fast-forward merges create no commit and skip the hook (use --no-ff to
gate them); conflicted merges fall back to the regular pre-commit and
pre-push gates.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds scripts/git/assert_main_merged.sh, which fetches the upstream main
tip and fails unless it is fully merged into HEAD. Wired in two places:
- pre-push: a new make-test-main-merged pre-commit hook (pre-push stage)
runs 'make test-main-merged' before every push.
- pull request: a new assert-main-merged job checks out the real PR head
(not the merge ref) and asserts the base branch is merged into it, so a
PR whose branch lags main goes red.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`make test` already runs at pre-commit. Re-running the full
suite at pre-push doubled the wait without adding signal —
the pre-push gate is now exclusively for `make test-signed`,
which is the cheap signature check the heavy hook was hiding.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`make test-signed` checks `git log -1 --pretty=%G?`; `N` (no
signature attached) aborts and the pre-push hook gates pushes
of unsigned tips. Sign locally via `git-sign-push` (already a
dev dep) or `git commit -S`.
Move the `.git` entry from a Makefile `echo … >> .dockerignore`
appendage into `.gitignore` itself (no-op for Git, required for
Docker). `make dockerignore` collapses to a single `cat .gitignore
> .dockerignore` which converges on the same content under any
parallel interleaving — previously 5 concurrent `make setup`
invocations from `make test` could append 3-5 stacked `.git`
lines and make pre-commit revert on "files modified by hook".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The URL-reachability probe now bounds its wall-clock end-to-end:
allow_redirects=False (a redirect chain previously stacked
timeouts), a 5h30m global deadline that marks in-flight probes
as warnings on expiry, and live `[done/total]` progress every 50
probes. Submission order is shuffled so same-host URLs no longer
batch at the front and self-DoS one origin.
The Docker image-version test drops its bespoke sequential
fetch loop and calls find_outdated_updates() from utils.update.docker,
so it now shares the INFINITO_WORKER_FETCH-sized worker pool with
the repository-refs check.
`scripts/tests/code/wrapper.sh` propagates BASH_ENV into the
container exec — without it INFINITO_WORKER_FETCH was unset
inside the container and the URL probe collapsed to 1 worker.
`make test` gains test-external; `make dotenv-force` regenerates
.env with a stripped environment so stale BASH_ENV-preloaded
INFINITO_* values cannot pin old numbers via setdefault.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`make test` and `make lint` now delegate to a generic parallel-make
wrapper that caps `-j` at INFINITO_WORKER_CPU, syncs per-target
output, and prints wall-clock elapsed time. `make test` gains the
full `lint` sweep and drops the phantom `test-deploy` target (PHONY
without recipe); pre-commit hooks consolidate around a single
`make test` invocation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
galaxy-importer's legacy-role mode was dropped upstream in 0.5.0
(CLI no longer accepts --legacy-role / --namespace), and the project
does not publish roles to Ansible Galaxy. The check enforces a
publishing schema that never gets exercised, against an abandoned API
that can only be kept alive via pinning galaxy-importer==0.4.39 +
setuptools<81 in an isolated venv. The cost has stopped buying
anything.
Removed:
- scripts/lint/galaxy.sh
- utils/install/lint/galaxy_importer.py
- install_isolated_pip_pkg() in utils/install/pip.py (only galaxy_importer
used it)
- Makefile lint-galaxy target + the entry in the `lint:` aggregate
- pre-commit make-lint-galaxy hook
- The galaxy_importer ensure() call in _install_ansible_tools
- Test assertion against cli.galaxy_importer in test_main.py
- Documentation rows mentioning lint-galaxy / galaxy-importer
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The `/username|email/i` regex literals in the bookwyrm row tripped markdownlint's
MD056 (column count) — markdown reads bare `|` inside table cells as cell
separators. Escape both occurrences to `/username\|email/i`. Also extend the
existing pre-push hook config so `make lint` runs alongside `make test-external`
before every push, so the next markdown drift never makes it past the push gate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* scripts/lint/autoformat.sh:
- rename run_sort_claude_settings worker to run_claude (matches the
renamed scripts/lint/claude.sh).
- drop the EXIT trap that wiped status_dir; cleanup happens explicitly
after `wait` returns, so a parallel worker that interrupts the
parent never races a vanished status dir.
- each worker now captures its tool's exit code and writes one of
OK / SKIP / FAIL. Tolerated `expected violations remain`-style exits
(markdownlint-cli2 rc≤1, ansible-lint rc≤2) still count as OK; only
real failures count as FAIL.
- all workers run to completion regardless of individual tool failures
(so the user always sees the full picture); after the summary, the
script exits 1 if any worker reported FAIL.
* scripts/lint/sort_claude_settings.sh -> scripts/lint/claude.sh
(history preserved via git mv); update test_claude_settings_sorted.py
fix-hint accordingly.
Plus working-tree drift across roles/, group_vars/, inventories/,
requirements/, and .pre-commit-config.yaml accumulated from the
make-test setup phase regenerating generated artefacts during recent
test runs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Makefile lint integration:
* scripts/install/lint.sh: replace the prior checkmake binary install with
ensure_mbake (mbake is pip-installable, parses our Makefile correctly
whereas checkmake bailed on .ONESHELL: / .SHELLFLAGS: / ifneq).
* scripts/lint/makefile.sh: new runner — `mbake format --check Makefile`
reports formatting drift and missing .PHONY declarations.
* scripts/lint/autoformat.sh: add a two-pass `mbake format Makefile`
(mbake's formatter is not strictly idempotent on the first pass).
* Makefile: split the multi-line `.PHONY:` block into 18 single-line
`.PHONY:` declarations so mbake's parser can validate it; add the
previously-missing `install-python-dev` and `build-dependency` PHONY
targets; add `lint-makefile` aggregate-and-direct target.
* .pre-commit-config.yaml: new make-lint-makefile hook scoped to
`^Makefile$`.
* .github/workflows/lint-makefile.yml: new CI workflow (pip-install mbake
+ make lint-makefile).
* .github/workflows/ci-orchestrator.yml: wire lint-makefile into
lint-gate.needs.
Misc:
* .gitignore: add roles/importer_result.json (tooling artefact written by
galaxy-importer when it runs in roles/).
Plus working-tree drift across roles/, group_vars/, inventories/, and
tasks/ that accumulated from the make-test setup phase regenerating
generated artefacts during recent test runs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* .pre-commit-config.yaml: add 6 scoped lint hooks (lint-action,
lint-ansible, lint-python, lint-shellcheck, lint-markdown, lint-galaxy)
with `files:` patterns so a markdown-only commit no longer triggers
ansible-lint or galaxy-importer. Aggregate make-test, make-test-lint
(md-only branch) and pre-push make-test-external are kept.
* Makefile: drop `lint` from the `make test` dependency chain. Per-file-
type lint stages now live exclusively in pre-commit; running them again
from `make test` would just duplicate work.
* scripts/lint/autoformat.sh: add markdownlint-cli2 --fix and
ansible-lint --fix passes alongside ruff/shfmt/shellcheck.
* roles/svc-db-openldap/meta/main.yml: collapse the wrapped description
into a single line (still under Galaxy's 255-char limit).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds default_install_hook_types so pre-commit install lays down both
.git/hooks/pre-commit and .git/hooks/pre-push from a single
make install-python-dev. Existing hooks pin to [pre-commit] so the
commit-time test surface stays unchanged; the new make-test-external
hook fires only at [pre-push] as a heavier gate before code leaves
the local clone.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Markdown-only commits previously skipped every hook because the single
make-test hook excludes .md/.rst/.txt. Add a guarded make-test-lint hook
that triggers only when no non-md file is staged, so md-only commits
still run lint. Mixed commits stay covered by make test via its test-lint
dependency.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>