Uh oh!
There was an error while loading. Please reload this page.
chore(build): add a Makefile so local checks predict CI (backend#1606) - #235
Merged
Conversation
tracebloc/.github runs real CI — actionlint.yml, the org code-quality suite against itself, four gate selftests and conformance-gate.yml — and had no Makefile, so there was no single local command that predicts it. That is the gap backend#1606 exists to close. `make check` (~19 s measured, green) runs the fast, offline subset with the same tools and the same flags as the workflows that own them: ruff --isolated --select E4,E7,E9,F . (code-quality.yml's no-repo-config fallback, reproduced not approximated) shellcheck --severity=error --format=gcc --exclude=SC1091, over the gate's OWN file selection (extension or shebang, .bats/.ps1 skipped) rather than a looser local glob house-rules ./scripts/house-rules.sh --all action-pins EXTRACTED from code-quality.yml's own python heredoc, so a supply-chain gate cannot have a second copy that disagrees with the one gating merges actionlint -no-color -oneline -shellcheck shellcheck selftests caller-drift, blocked-marker, standards-sync, version-bump-gate — all four, which CI runs only behind paths: filters Two CI steps are deliberately NOT in `check`, each named in the file with the reason: gitleaks is in `check-all` (CI installs it per run from a pinned tarball; it is on no dev machine by default, and a credential scan that gets quietly skipped reports clean), and conformance-gate.yml is in neither because it polls the API for a verdict on a pushed head sha — there is no sha before you push. `make audit` runs caller-drift's token-needing half on demand. `setup` preflights the tools and installs a pre-push hook that runs `make check`; it installs nothing, because this repo has no venv or lockfile to install a pin into. Refs backend#1606. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Uh oh!
There was an error while loading. Please reload this page.
The audit target invoked `caller-drift.py` bare. Both --inventory and --source-dir default to exactly what caller-drift.yml passes, so it was equivalent today — but this file's claim is that its commands are COPIED from the workflow, not that they happen to agree with it. A default is precisely the kind of thing that moves under you, and the drift would be silent. Refs backend#1606. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rs (backend#1606) The shellcheck recipe piped `git ls-files` into a `while` loop under `set -e`. A pipe's exit status is the last stage's (the while loop), so a failed `git ls-files` was swallowed: the selection file stayed empty, the recipe printed 'no shell files in scope' and exited 0 — a clean report with nothing scanned. Materialize the listing to a temp file first (as code-quality.yml does), so a failed listing aborts the recipe non-zero instead of reporting a false clean.
LukasWodka
commented
Aug 12, 2026
ContributorAuthor
bugbot run |
Uh oh!
There was an error while loading. Please reload this page.
…e check (backend#1606) The Makefile target ran only blocked-marker-selftest.py, but the CI selftest job (blocked-gate-selftest.yml) also runs blocked-marker.py against a title containing the gate's own filenames — the only coverage for a self-reference bug that matches the word 'blocked' inside a path rather than in prose. Without it, make check could report green on a matcher change that would fail the gate's selftest in CI. Mirror the second step so local checks predict CI.
LukasWodka
commented
Aug 12, 2026
ContributorAuthor
bugbot run |
Uh oh!
There was an error while loading. Please reload this page.
…(backend#1606) version-check compared actionlint -version's first line verbatim to ACTIONLINT_VERSION (1.7.12). Release and Homebrew builds print '1.7.12', but 'go install' — the Linux hint on guard-actionlint — prints 'v1.7.12' from Go build metadata, so a matching toolchain still warned. Strip a leading v before comparing, the same cries-wolf class already handled for ruff. Verified: make version-check is clean with 1.7.12 on PATH, and v1.7.12 normalizes to 1.7.12.
LukasWodka
commented
Aug 12, 2026
ContributorAuthor
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 317a8e9. Configure here.
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This repo publishes the org's reusable workflows and runs real CI against itself —
actionlint.yml, the org code-quality suite, four gate selftests,conformance-gate.yml— and had noMakefile. So there was no single local command that predicts CI, which is the gapbackend#1606exists to close. 14 of 20 repos already have one; this was one of the two notable holdouts with real CI behind it.Adds a
Makefilewith the house target set (help,setup,check,check-all), matching the shape ofbackend/Makefile,e2e-test-agent/Makefileanddocs/Makefile:.DEFAULT_GOAL := help,?=-overridable tool vars, pinned versions mirroring the workflows, per-tool guards with install hints, and theinstall-hookspre-push hook.It is a thin wrapper. Every command is copied from the workflow that already runs it — no new tool, no new config, no new rule.
What
make checkruns, and where each command comes fromruffruff check --isolated --select E4,E7,E9,F .code-quality.ymlruff jobshellcheckshellcheck --severity=error --format=gcc --exclude=SC1091over the gate's own file selectioncode-quality.ymlshellcheck jobhouse-rules./scripts/house-rules.sh --allcode-quality.ymlhouse-rules jobaction-pinscode-quality.ymlaction-pins jobactionlintactionlint -no-color -oneline -shellcheck shellcheckactionlint.ymlselftest-caller-driftpython3 scripts/tests/caller-drift-selftest.pycaller-drift.ymlselftest jobselftest-blocked-markerpython3 scripts/tests/blocked-marker-selftest.pyblocked-gate-selftest.ymlselftest-standards-syncpython3 scripts/tests/standards-sync-selftest.pystandards-sync.ymlselftest jobselftest-version-bump-gatebash scripts/tests/version-bump-gate-selftest.shversion-bump-gate-selftest.ymlThree details worth review, because each is a place a lazier version would silently check less than CI:
shellcheckreproduces the gate's file selection, not a glob. The job does not take*.sh— it takes every tracked file whose extension is.sh/.bash/.kshor whose first line is a sh/bash/dash/ksh shebang, and explicitly skips.bats/.ps1/.psm1/.zsh. A looser or tighter local glob checks a different set of files and then disagrees with the gate. Today it selects 2 files.action-pinsis extracted fromcode-quality.yml, not reimplemented. The job body is a python heredoc. A hand-copied second version of a supply-chain gate is a version that can silently drift from the one actually gating merges — the exact#1606failure mode, in the file where it would cost the most. Extraction runs the gate's own source byte for byte, so drift is impossible by construction. This follows the existing house pattern:scripts/tests/version-bump-gate-selftest.shextracts its gate'srun:block for the same reason. The extraction refuses to guess — exactly onepython3 - <<'PY'block must exist and the extracted body must be non-empty, or it fails loudly with a pointer to fix the target.All four selftests run, where CI runs each only behind a
paths:filter. A PR that refactors a gate without touching its named trigger paths never runs its selftest in CI. Locally there is no filter and no reason for one — they cost ~15 s together, ~15 of which isversion-bump-gate(it builds a throwaway git repo per case).What did NOT go in
check, and whyBoth are named in the Makefile with the reason at the target, not silently omitted.
gitleaks→check-all. CI installs it per run from a version- and SHA-256-pinned release tarball; it is on no developer machine by default. Makingcheckdepend on it would either fail every clean checkout or — worse — get itself quietly skipped, and a credential scan that is skipped is a credential scan that reports clean.check-alltherefore hard-fails with an install hint rather than degrading:conformance-gate.yml→ neither, and it cannot be. It does not inspect the tree at all: it polls the GitHub API forcaller-drift.yml's verdict on the PR head sha. There is no head sha and no run to poll before you push. What it ultimately gates on is local, though, andcheckruns it: the caller-drift selftest, the half that needs no token. The other half (audit) reads ~20 repos' workflow files through the API, so it is a separate namedmake audittarget — deliberate, not pre-push, and not a check on this tree (its answer can change with no local commit).The two crons (
kanban-reconcile,kanban-archive) andstandards-sync's audit half are likewise token-gated live-org jobs, not pre-push checks; the file says so.blackis correctly absent:code-quality-caller.ymldoes not passformat: true.Proof:
make checkis green, measured19.0 s, well inside the 60 s budget. Breakdown:
version-bump-gateselftest ~15 s, everything else together under 3 s (ruff 0.16 s, shellcheck 0.32 s, house-rules 0.45 s, action-pins 0.06 s, actionlint 0.68 s, the other three selftests <1 s each).I also verified the target actually fails rather than passing vacuously: injecting an undefined name into a throwaway
.pyreddensmake ruff(Found 2 errors./make: *** [ruff] Error 1), andmake setup/make install-hooksinstall the pre-push hook and are idempotent.One bug caught and fixed before pushing
The pins were first written with trailing inline comments (
RUFF_VERSION ?= 0.15.20 # ...). make strips the comment but keeps the whitespace ahead of it, so the version was"0.15.20 "— andversion-checkthen warned that ruff0.15.20differed from the pinned"0.15.20 "on a machine that matched exactly. A version guard that cries wolf is one people learn to ignore. Comments moved above the assignments;make version-checkis now silent on a matching toolchain. The reason is recorded in the file so it does not come back.Notes on
setupsetupinstalls nothing — this repo has no venv, lockfile or package to install a pin into. It preflights every toolcheckneeds, warns (does not fail) when a version differs from the pin CI uses, and installs the pre-push hook. A hard version failure would block a contributor with no in-repo way to comply.Conflict check
Checked both open PRs' file lists first. No overlap — neither touches
Makefile:feat/1608-conformance-one-screen):scripts/caller-drift.py,.github/workflows/caller-drift.yml,scripts/tests/caller-drift-selftest.py,.gitignorerelease-train/to-main): the staging→main promotionThis PR adds exactly one new file and modifies nothing. Note in particular that
.gitignoreis untouched (#227 owns it): the extracted action-pins script goes to amktempoutside the tree, so there is no new build artifact needing an ignore rule.make checkinvokesscripts/tests/caller-drift-selftest.pyandscripts/caller-drift.py, which #227 changes — but by path, not by content, so whichever merges second simply runs the newer version.Test plan
make checkgreen in 19.0 s on this tree (output above)make ruffgoes red on an injected finding — the target is not vacuousmake check-allhard-fails with an install hint when gitleaks is absentmake setup,make install-hooksinstall the hook; re-running is idempotentmake version-checksilent on a matching toolchainmake helplists every target, including what is not locally reproducible.py, not shell-classified, not a workflow), so it cannot itself redden the gateRefs backend#1606.
🤖 Generated with Claude Code
Note
Low Risk
Adds only developer tooling (Makefile and git hook); no workflow, gate logic, or runtime behavior changes in CI.
Overview
Introduces a new root
Makefileso this repo matches the org’s standard developer entry points (help,setup,check,check-all) and closes backend#1606’s gap: one local command that should agree with CI.make checkruns lint (ruff, shellcheck with the same tracked-file selection ascode-quality.yml, house-rules, action-pins, actionlint) and all four gate selftests unconditionally—unlike CI, where those selftests are often skipped bypaths:filters.make check-alladds gitleaks over full git history;make auditis a separate, token/API target for live org caller-drift and is intentionally not pre-push.Notable fidelity choices: action-pins is not duplicated—the target extracts the single
python3 - <<'PY'block fromcode-quality.ymland runs it withSOFT_FAIL=false; tool versions are pinned to match workflows with warn-onlyversion-check**; **make setup** preflights PATH tools and installs a **pre-push hook** that runsmake check(skips branch deletes, missingmake`, and non-owned existing hooks).conformance-gate.ymlis documented as not locally reproducible (API poll on pushed SHA); local coverage is the caller-drift selftest instead.Reviewed by Cursor Bugbot for commit 317a8e9. Bugbot is set up for automated code reviews on this repo. Configure here.