cmd/redscan |
Every default branch whose last run failed. One pass: 1819 repositories, 44 red — all of them stale Renovate lanes whose last run predated a token rotation by three hours. A red branch is not a broken one; check WHEN it last ran. ⛔ Until 2026-10-11 it read no arguments at all, so redscan -h, run to see its flags, was a full sweep of every organisation and had to be killed. It now prints its usage, refuses anything else with status 2, and TestEveryCommandReadsItsArguments fails any command that cannot answer -h. ⛔ It asks for the hourly budget before spending it, through internal/fleet's guard, and refuses to begin below a floor of 3000 — one call per organisation plus one per repository is about 2000, and that budget is per user, shared with the fleet's Renovate runners. Four sweeps have emptied it and made those runs fail, the fourth with the note about the first three already written, which is why the control is in the tool now and not in a note. When the pass runs down to the reserve it stops and says so in its own words, separately from the unread list: both arrive as an error from the same call, and filing a deliberate stop under "could not be read" reads as a fault in the fleet and calls for the wrong action — an unread repository is asked again, a budget stop is waited out. The reserve is a chosen number, not a measured one: what is measured is the cost of a sweep, and what one Renovate run costs is not. ⛔ And it does not protect against the secondary limit, which rate_limit is blind to — measured 2026-10-10, gh answered 403 on every call while the budget read 5000/5000. |
cmd/prscan |
Open pull requests across every org. Batches ~15 org: qualifiers per search query, so ~20 calls instead of 320 — the search API is rate-limited far more tightly than REST. |
cmd/flagcensus |
Which argument-parsing idiom each command here uses, and which of its flags would need two dashes after a move to cobra. Measured 2026-10-31 over 54 commands: cobra=24 flag+help=21 flag-global=6 hand -h=3, 144 flags of which 137 need two dashes. Two of those rows are worth having beyond the migration: flag-raw is zero, so nothing is left in the state where -h arrives as an error — fleet.ParseFlags closed that — and UNREAD is zero, which is the state redscan was in when -h started a full sweep. ⛔ The split is six ways and not four because a first version labelled six commands with a defect they do not have: it called the package-level flag.Parse() and a ContinueOnError FlagSet both "flag" and said -h arrives as an error, which is false for the global set (ExitOnError answers -h with 0 itself). What those six lack is a run() a test can drive. ⛔ And a registration has a name or a usage string: the receiver is not type-checked, so every x.String() looked like a flag and a first count said 149 where there were 113. ⛔ It generates nothing — a blanket rewrite of 54 files is how a campaign here once replaced a helper with a call to itself — and flagcensus mains <command>... prints just the entry points a migration rewrites. ⛔ Its two ancestors were written in a scratchpad on 2026-10-18 and were gone with the next reboot, so the same counts had to be derived from nothing thirteen days later: a measurement worth quoting is worth a repository. |
cmd/prmerge |
Merges dependency PRs that are mergeable and fully green. |
cmd/tagscan |
Every tagged Go module whose default branch is ahead of its latest tag — work that is merged and that go get cannot reach. Built after the same mistake twice: ghmerge alone on go-widgets/toolkit (2026-09-05) and on go-pdfkit/ops (2026-09-07), the second time with the rule already written down. It asks about tags, not releases: go get resolves a tag, and a first draft asking /releases/latest reported ops as behind fifteen minutes after v0.12.0 had been pushed and proved to resolve. git ls-remote costs no API budget, and a tag that IS the head settles without a call. |
cmd/quietscan |
Every organisation whose Renovate runner has stopped, runs and fails, or asks for a secret that does not exist. redscan asks whether the last run FAILED; a runner that stopped does not fail, it says nothing — so this checks WHEN it last ran. And then WHAT BECAME OF IT: sixteen runners were once reported healthy while every nightly run failed in ten seconds. |
cmd/judgescan |
Every repository whose tests look for an external tool its CI never installs — a foreign judge that exists in the source and has never once run. Written after the same defect surfaced twice in one afternoon: go-fde/luks had a cryptsetup interop test gated on root (formatting a file needs none; only attaching a device does), and go-filesystems/btrfs had seven oracles whose btrfs check half — upstream judging OUR WRITES — was locked behind the same borrowed root gate, in a CI that installed no btrfs-progs. Both looked like a green lane over a file full of assertions, judging nothing. Reads the working tree only, so it cannot starve Renovate: its ONE call is the staleness check below. Knows that a step says poppler-utils and a test says pdftotext, and that a checkout path: btrfs is not an installation of btrfs-progs — the false negative that would have erased its own founding case. |
.github/workflows/docs-current.yml |
Reusable. An organisation's landing repository calls it and the check runs THERE, with no secret — listing a public organisation is a public read. Nine lines in the caller — ⛔ not on: [push, pull_request], which both fire for a branch with a PR open and ran the check twice on the first three organisations that copied the snippet. |
cmd/docscan |
Every repository an organisation has but does not advertise — and every one it advertises and no longer has. First pass (2026-09-25): 334 organisations, 87 modules named on no surface. 2026-09-26: 0 organisations with drift, with the check adopted in eighteen organisations so it stays there — re-measure with go run ./cmd/docscan rather than trusting this line. Five of its six hardenings came from FALSE POSITIVES, which cost more than a miss: an entry carrying org = "<other-org>" is not a claim about this one, a nav line may name its module in its title (- The client (reddit): client.md), an archived repository is not a gone one, a YAML flow mapping on one line is a list the reader could not parse, and a PR check reading the published page judges main rather than the branch. Two definitions of drift — the printed count and the exit status — disagreed in both directions until one method served both. |
cmd/pagespublish |
Puts one documentation build into a checkout of a gh-pages branch, the way mike did: the version replaces <pages>/<version>/ at the branch root and nothing else, then versions.json (mike's format, which the Hextra fork's version selector reads), the latest link and the root redirect are rewritten from the versions present. ⛔ It exists because a hand-written publish step got the path wrong in 76 repositories at once. The workflow mkdocs2hextra generated copied the built site to docs/<version>/ inside gh-pages — but a project repository named docs is already served at /docs/, so the site landed at /docs/docs/main/ while /docs/latest/ went on serving the MkDocs build mike had left there. Both URLs answered 200, which is why nothing caught it: the site was published, just not where anything links — found by fetching the page, not by reading a green deploy. ⛔ And it is Go rather than the shell script it replaces: the fleet's reference site carries a 60-line scripts/publish-version.sh, and copying that into 76 repositories would be 76 copies of untested shell. One command, one test suite, one line in every workflow. It prunes the misplaced docs/ once, and only when that directory holds nothing but version directories — a docs/ with an index.html of its own is somebody's page and is left alone. 0.10 sorts after 0.9 (compared as numbers, which string order gets wrong), main is last and marked development, a version directory that was a symlink becomes a real one without taking its target with it, and the build must have an index.html before anything is replaced, because publishing an empty directory over a live version cannot be undone from the branch. |
cmd/sitemigrate |
Moves one docs site checkout (Hugo, tannevaled/hextra) to the fork's native version selector and Go page history: sitemigrate --tag v0.21.0 DIR removes the site's local layouts/_partials/custom/version-select.html and its .fs-version-select CSS rule, points the menu item at components/version-select.html (adding params.versions.development: "dev" when the site relied on the old default), and replaces sh themes/hextra/scripts/page-history.sh > by go run github.com/tannevaled/hextra/cmd/pagehistory@TAG > in the workflow, README and .gitignore. It edits files only (the submodule bump, commit and PR are the caller's) and refuses a tree it does not recognise. Used for the fleet's docs sites on 2026-10-27. |
cmd/mdpdf |
Renders a Markdown report to a print-styled PDF with the fleet's own tools and no browser: mdpdf --in report.md [--out report.pdf] [--title T] [--author A] [--paper a4|letter] [--css extra.css] [--toc] [--toc-after H] [--toc-title T] [--lang fr] [--page-numbers=false] [--allow-network] runs go-kramdown (pipe tables included), then go-pdfkit/html2pdf over a built-in print stylesheet, and fills the PDF's Title and Author. It renders offline by default (html2pdf.Offline, html2pdf v0.4.0): a remote image or a <link> in the Markdown is not fetched and no file: URL is read, so a Markdown file from elsewhere cannot make the command reach a URL. data: images still render, and --allow-network fetches remote resources (a test checks both, the second being the control). Input is bounded (16 MiB of Markdown, 1 MiB of CSS); --out defaults to the input with a .pdf extension. --page-numbers (default on; =false to remove) prints "n / N" at the bottom centre of every page, and --toc adds a table of contents from the h2 and h3 headings, each with its page number, after the title block by default, where a <!-- toc --> line stands in the Markdown, or at the end of the section named by --toc-after HEADING (exact NFC text, case-sensitive, else a slug or id match; an unknown heading is an error naming the available ones; it implies --toc and wins over the marker). The entries always list every h2 and h3, the named heading included. The page numbers come from html2pdf's second layout pass, so they hold wherever the table sits. All of it is: both are go-pdfkit/html2pdf v0.2.0 features (@page margin boxes, counter(page), target-counter()). ⛔ One named workaround remains (cmd/mdpdf/workarounds.go), to be deleted when go-webengine/engine#322 closes: a disabled checkbox is laid out but not painted, so go-kramdown's GFM task <input type="checkbox"> becomes ☑ / ☐. List markers are painted by go-webengine v0.18.0 (html2pdf v0.3.0), so the HTML carries none of its own (a test checks bullets and numbers print exactly once). The two other workarounds are gone: a table after a list was fixed in go-kramdown v0.3.1 (kramdown#12, PR #14), and [x] task items are GFM syntax that go-kramdown renders under Options.Input = "GFM" (kramdown#13, closed as not a bug). --lang fr sets <html lang> and the default --toc-title (fr, en, de, es, it; "Contents" otherwise); --toc-title overrides it. Tested end to end: the PDF is read back with go-pdfkit/reader and go-pdfkit/extract for its Title, Author, page count, MediaBox, footers and table of contents numbers; FuzzPreprocess runs the whole Markdown-to-HTML step (with and without the table of contents) without panic. |
cmd/docsversions |
Builds every published version of a versioned Hugo documentation site with one look — the default branch's — and puts the builds in a GitLab generic package registry: docsversions build --url https://gt-cloud.resinfo.org/docs then docsversions publish --package gt-cloud-docs --archive gt-cloud-docs.tar.gz (in CI the API, project and job token come from CI_API_V4_URL, CI_PROJECT_ID, CI_JOB_TOKEN). A version is its content directory at a ref — development at the default branch, and every strict vX.Y.Z tag, newest first, compared as numbers; everything else (theme submodule, hugo.yaml, layouts, assets, i18n, static, logos) is the default branch's, checked out once in a detached worktree with its submodules. ⛔ It exists because the look was frozen into each release: GT-Cloud's /docs/ served v1.0.3, built from a release branch that predated the logo menu, the language icon and the theme combo; three patch releases made only to carry a favicon, a logo and a colour to the default page each re-published the OLD interface around them, and the user found a RESINFO link in the wrong place. "The look should be independent of the version." Measured on GT-Cloud: all six versions (v1.0.0 → v1.1.0, development) build under --panicOnWarning with the current look, each with its own content — v1.0.0 has the logo menu and the kit's colours, and none of the page descriptions added after it. Creation dates and authors come from the theme's Go page history program (go run github.com/tannevaled/hextra/cmd/pagehistory@v0.21.0, stdout kept apart from its progress lines), run once for the default branch's tree. A version that fails to build is reported, the others are still built, and the run exits 1 rather than publishing a partial set silently; a content archive entry that would leave the tree is refused before it is written. The archives keep tar -C dir .'s layout, which the showcase's deploy script extracts. |
cmd/pagesretire |
Removes the version directories a documentation branch kept from MkDocs, so that latest names the Hugo build. ⛔ It exists because the migration succeeded and nobody could see it. All 77 converted sites publish a Hugo build — under params.docs.version, which is main for a repository with no release tagged — but mike had left a release directory (0.1, sometimes also 0.2) holding the MkDocs build, latest means the newest release, and the branch root redirects every reader to latest/. Measured 2026-10-20: 75 of 77 sites served MkDocs at the only URL anything links to, and the two exceptions are precisely the two repositories that never published a release, where the alias fell back to main. Neither the merge tally (77 PRs landed) nor the deploy logs (green) could show this; only fetching /docs/latest/ could. ⛔ And it refuses rather than guesses: removing a published version cannot be undone from the branch, so every page under the directories it would remove must be shown to exist in main/ first — across the fleet that read 514 pages against 673 and found none missing, but it is re-run per repository, because "it held for the others" is not a measurement of this one. It also refuses a branch with no main/, one whose main/ is itself still MkDocs (the shape a half-run migration leaves), and a directory it could read no pages from — an unreadable directory and a duplicated one otherwise both report "nothing missing". versions.json, the latest link and the root redirect are then rewritten through the same internal/pages rules pagespublish uses, so the two cannot drift apart on what latest means. |
cmd/mkdocs2hextra |
Converts an MkDocs Material site to Hugo with this fleet's Hextra fork, pinned at v0.16.0 as a submodule. It exists because the fleet has 85 of them, 575 pages — counted, not estimated — and the conversion is the same five mechanical steps every time, four of which are invisible when done wrong. 1 252 links to a .md file: Hugo serves /page/, not /page.md, so every one is broken by the move; they become {{< relref >}}, which resolves at BUILD TIME and fails the build when the target is missing instead of 404ing for a reader later. 209 admonitions in nine kinds → the callout shortcode, with the collapsible ??? form → details; the kind map is the census of what actually occurs, and success (41 of the 209) becomes info because Hextra has no colour for it — a grey box is a cosmetic loss, a shortcode nobody defines is a build failure. 24 content tabs, 84 attribute lists dropped and counted. And 82 of the 85 used mike, which was doing something real: each documented version has its own path, so the generated workflow keeps one (params.docs.version → --baseURL) and a deploy replaces only that version. ⛔ Everything else under docs/ is content too, and missing it loses more than the links do: MkDocs serves docs/ as the site root, so docs/assets/fs/x.png is served at /assets/fs/x.png while Hugo serves static/ that way — a first version moved only the pages and lost 53 images on go-filesystems/docs, and the local build did not care, because a missing image is not a build error in any static site generator. A .md the nav never names is still built by MkDocs and still reachable, so those are carried over too, after the listed ones, and named in the report. ⛔ It previews by default and writes with --apply, and it never deletes: the old mkdocs.yml, requirements.txt and docs/ are listed for the person to remove in the same commit, because a tool that removes the old site and writes a broken new one leaves nothing to go back to. The conversion is line-based and fence-aware rather than a Markdown round trip, so that the diff of a migration is readable and a shell example containing === "x" stays an example. ⛔ Three defects came from the FIRST REAL SITE and no fixture had shown any of them: a nav title containing a colon (- "Second architecture: riscv64": asmgen/riscv64.md, five times on go-asmgen) was split at the wrong place, so five pages were reported missing while sitting in docs/; a section's title was turned into a directory on top of the one its pages were already in (content/en/asmgen/asmgen/design.md), which would have broken every relative link; and the generated config carried two deprecations inherited from copying an older Hextra site in this fleet — cascade._target (Hugo 0.156) and languageCode (0.158) — both refused by the pilot's --panicOnWarning build and now held by a test. |
cmd/pagescheck |
Whether a GitHub Pages site serves what its repository builds. ⛔ A green deploy is not a served page: go-ooxml.github.io served the default Jekyll theme's rendering of README.md — 5 512 bytes of it — while deploy-pages built the Hugo site, uploaded the artefact and reported success three times. Nothing was broken; the setting that decides which of the two is published (build_type) lives in the API rather than in any file, so no file was wrong and no check looked at the URL. This one reads the page, because a workflow can only say what it did. First pass (2026-10-12): 622 Pages sites, 15 serving GitHub's own build of a README, three more serving the branch the source lives on, one unreadable. ⛔ Two refinements came from FALSE POSITIVES in the first five minutes, both of which would have made it unusable: a documentation root is often a redirect (mike writes a stub that refreshes to /latest/, and the stub has no generator meta), and a branch source is not automatically a defect — mkdocs and mike push a built site to gh-pages, which is an artefact under another name, so what is reported is serving the branch the source lives on. The generator matcher reads the minified spelling (name=generator, no quotes), since hugo --minify writes that and a matcher taken from the template source would match nothing on every page in the fleet. Reading a published page is not an API call and is deliberately not charged to the budget Renovate shares; has_pages comes with the repository listing, so finding the sites costs no call of its own. |
cmd/pagetext |
Whether a published page now contains the text we wrote: pagetext URL want... [-- URL want...] prints the status and true/false per string, exit 1 on a non-200 or a miss. The last step of "every user-visible change updates README, landing and docs, and the published page is checked". ⛔ It exists because the first check after a deploy said the new text was missing when it was not: GitHub Pages' CDN was still serving the previous build, and the same URL with ?nocache=<time> showed the text (RFC 9111 §2: a cache key is at least the method and the target URI, and many caches use the URI alone, so a different query is a different entry — the RFC text read 2026-10-18; that it was the cause here is what was measured). So a cache-busting parameter is added by default (-no-cache-bust disables it) and -retry N -wait 20s polls, bounded, until the deploy is visible. The match is on the page's text — tags dropped, entities decoded, whitespace collapsed, script and style ignored — because Hugo escapes and minifies and --output can arrive split by tags; -raw matches the bytes. Redirects are followed and reported; one response is read up to 16 MiB, one request times out at 20 s. ⛔ And a second false negative of the same family, 2026-11-01: it reported the text missing on a page that already carried it, six retries running, because of an apostrophe. The landing said 262 of this fleet's own; Hugo printed fleet’s; this command decodes entities and drops tags but did not fold typographic punctuation, so every English possessive and contraction — most sentences we write — was a miss. Goldmark's typographer is on by default in Hugo and substitutes the apostrophe, both quote pairs, --/--- and ..., so those are now folded on both sides before matching. A verification tool that answers "not published yet" about something published is worse than no tool: the next person reads a real failure the same way. The fold deliberately makes an em dash match a hyphen — this is a presence check over prose — and -raw stays exact. Tested in both directions, with a negative control that an absent string stays absent. Deliberately not pagescheck (does the site serve what its repository builds), and not an API call, so it never touches the budget Renovate shares. |
cmd/sitelinks |
Whether every local link of a built static site leads to a file of that build: sitelinks --base /docs/development/ public/ reads every .html, takes href, src, poster and srcset, resolves each local one against the page's own URL under --base, and needs a file there (or a directory with an index.html). External links are not fetched; a root-absolute link outside --base cannot be judged from this build, so it is counted as outside, never as broken. ⛔ It parses the HTML, it does not grep it: the regular-expression version it replaces reported two broken links on every page of a Hugo site — e.docsRoot+t.version+ and a minified line of the version selector, both read as an href= inside an inline <script>. Attributes come from x/net/html's tokenizer, where script text is never an attribute. Measured on the GT-Ceph documentation build: 117 pages, 6 668 local links, 0 broken (the old version: 2 per page, both false); with one PDF removed, that PDF reported broken on the 4 pages citing it. A build with no page is exit 2: nothing checked is not "no broken link". Complements pagetext, which reads the published page. |
cmd/treecmp |
Whether two directory trees hold the same files, byte for byte: treecmp GENERATED REFERENCE (--both also reports files only in REFERENCE). The question a regenerated or moved artefact raises — "does this generator, run again, give back what is committed?". Files are compared in 64 KiB chunks, so two 4096-px renders are not read whole. ⛔ A byte difference is not yet a defect: moving the GT Green-IT brand kit to its own repository and regenerating it there differed in 65 of 73 files — and in none of its 51 PNGs pixel for pixel (pngeq): a newer encoder had compressed the same pictures differently. So treecmp names which files differ and stops there. An empty GENERATED tree is exit 2. |
cmd/pngeq |
Whether the PNGs of two trees hold the same pixels: each pair is decoded to 8-bit straight-alpha RGBA and compared channel by channel (--tolerance N accepts differences up to N). It exists because treecmp's "65 of 73 files differ" on a moved brand kit read as "the kit does not reproduce", which was false: 51 PNGs, 0 channel values different. A size mismatch, a missing counterpart or a file that does not decode is a difference, never skipped; a tree without a PNG is exit 2. ⛔ Pixel equality between two renders by the same engine cannot see what that engine gets wrong for both: go-gfx ignores SVG opacity (go-gfx/gfx#77), so a master compared with its source "matched" with the shadow's opacity mutated. Pin an absolute value from the source's own numbers as well. |
cmd/glmerge |
Merges a GitLab merge request when, and only when, it is open and mergeable, its head is the full SHA that was reviewed and tested, and its head pipeline succeeded on that SHA — prmerge's rule for the GitLab side of the fleet (plmlab hosts the RESINFO working groups): glmerge -f TOKEN_FILE group/project IID SHA. The merge itself is pinned (sha=, merge commit, no squash, branch kept), so a push landing between the check and the merge makes GitLab refuse. It waits while GitLab is still checking. --no-pipeline is for a project without CI, and is said in the output rather than implied. The token is read from a file and sent in the PRIVATE-TOKEN header only — never printed, never in a URL, an argument or the environment. ⛔ It replaces a shell function retyped in three sessions, lost twice when /tmp was cleaned. Tested against a fake GitLab: every refusal (another head, a failed pipeline, a pipeline on another commit, none, closed, conflict) leaves it unmerged; on the real plmlab it refused an already-merged MR with exit 1. |
.github/workflows/brand-complete.yml + cmd/brandcheck |
Reusable. Whether a brand repository carries what its README promises, and whether the mark is still in the family. ⛔ Written because a brand repository is assets and a README — nothing compiles and there are no tests, so it had no checks at all, and ghmerge rightly refuses a pull request nothing has run against. The only way to merge one was to go around the tool that exists to stop unreviewed changes. It checks the inventory (three colourways × eight sizes, plus ico, icns, avatar, social) and four grammar rules: a 256-unit plate, a rounded corner, a two-stop gradient, a glyph drawn in lines. ⛔ It does not check the stroke width against a number: measured over the 312 organisation marks there is no fleet-wide one — 13 is the commonest, 22 appears twenty times and 10 appears once — so a constant would fail most of the fleet and teach everybody to ignore the tool. A note claiming the family width was 10 "verified on the seven existing marks" was wrong on both the number and the population. |
cmd/testscan |
Every repository whose CI runs go test on only PART of what has tests — or on nothing at all. Written after openweft/weft: five workflows, go test in one of them naming four packages, and 83 packages with tests. Every lane green, about four of them. Switching on a plain go test ./... there found two tests that could not pass on Linux at all. One pass: 884 repositories, 519 have tests, 482 run them all. Reads the working tree only, so it cannot starve Renovate: its ONE call is the staleness check below. It follows a task ci into the Taskfile rather than calling it absent, and reports a computed package set (go test $PKGS, go list | xargs) as unresolved rather than as zero — the false positive that named three dozen healthy repositories on its first run, including one fixed an hour earlier. |
cmd/vscan |
For every tagged module whose branch has moved past its tag, what the next version should be — derived from the public API with gorelease, not guessed. tagscan found 280 of 333 tagged modules carrying merged work go get cannot reach; choosing 280 bumps by hand is 280 chances to publish a number that lies about compatibility. It asks once per GOOS, which is not a refinement: removing New from go-xrkit/depth3d's !darwin file — a break for every Linux consumer — reads as v0.1.1, a patch, on a darwin host, and as v0.2.0 with an incompatible change on linux and windows. It reads the default branch, never the checkout: a first version ran where the clone happened to be and called go-simd/floats a patch when its own branch head was a minor. And it records the incompatible-changes section separately from the number, because in v0.x an addition and a removal both suggest v0.2.0. A frozen base — a tag git tag --merged cannot see, left on an orphaned line by a history rewrite — gets its own section rather than reading as never tagged: go-filesystems/xfs v0.1.0 is published and required by six repositories, and "never tagged" is an invitation to cut it again. gorelease still answers for those, because it fetches the base from the module proxy and never consults git ancestry. When gorelease cannot load the module graph at all -- go-highway v0.0.12 requires its own hwy/goat at v0.0.0 through a replace Go ignores in a dependency (ajroetker/go-highway#73), so go list -m all fails for every module downstream of go-images/jpeg2000 while go build works -- it falls back to apidiff on the same three platforms and applies gorelease's own rule (requirementsChanged included) to the result. That fallback answers only that one refusal, is never "safe", and the report says which modules it derived and why: on 2026-10-11 it read desktop, gallery, isoicons and tui, which gorelease had left UNREAD for a day. Since v0.46.1 it also answers when it is the base that cannot be loaded: a module that has since dropped go-highway still has an older tag that requires it, and gorelease then reports preparing to load packages for M@vX with the cause, unknown revision, at the end of a requires chain several lines down. vscan used to keep only the first line, a harmless deprecation warning, so jpeg2000, gfx and render stayed UNREAD on 2026-10-18. It now keeps the chain. A base tag that simply does not exist is also an unknown revision, but without a chain, and it stays unread. |
cmd/gopin |
Replaces go-version: stable in a repository's workflows with an explicit version, and raises go.mod to match. stable is a channel, not a version, and that is the defect: measured on go-pkgx on 2026-10-04, CI had been building and running on go1.27.1 — Setup go version spec stable → go version go1.27.1 in the job log — while all seven go.mod still said go 1.26.4. A minor Go bump, the one that reindents under gofmt and changes coverage block counting, arrived with no commit and nothing to review. Worse, the organisation's shared Renovate preset carries a rule written for exactly this — dep name go under gomod and github-actions, automerge: false, groupName: null, citing golang/go#81000 — and it had never produced a pull request, because an alias has no version to compare or rewrite. The contrast is demonstrable rather than argued: in one ci.yml (go-pkgx/registry-viewer) the build job said stable and Renovate did nothing, while the bricolint job said "1.26.4" and Renovate opened a pull request moving it to 1.27.1 — same file, same run, one variable. So pinning is not rigidity: it is the condition for the review rule to apply at all. It reads the default branch through the API, never a checkout — a local sweep still reported three go-pkgx repositories as holding the alias hours after all eight were pinned and merged. It refuses to touch a job that already names a version, even an old one: somebody chose that, possibly to hold a repository back. And when that version is OLDER than the target it also holds go.mod where it is, because raising the directive past the pin does not merely look inconsistent with it, it breaks it: with GOTOOLCHAIN unset — the default setup-go leaves in place — go satisfies a directive it is too old for by DOWNLOADING the newer toolchain, so the lane keeps printing the pinned version in its setup step while running the one it was pinned away from. Measured on go1.26.4 against a module asking for 1.27.1: GOTOOLCHAIN=go1.26.4+auto runs go1.27.1, while GOTOOLCHAIN=go1.26.4 refuses with go.mod requires go >= 1.27.1. go-gfx/gfx is the live case — its loong64 lane names go1.26.4 because golang/go#81000 miscompiles that package there and the 1.27 backport is still open — and it is reported as held rather than changed. A pin naming no patch (1.27, 1.27.x) is NOT a hold inside its own minor, because setup-go resolves it to the newest patch there — measured on cloud-boot/tamago-uefi, Setup go version spec 1.27.x → go version go1.27.1 — so only 1.26 and below hold. Reports by default; -apply opens the pull requests, paced because GitHub's SECONDARY limit brakes a burst. Local census 2026-10-04: 376 of 620 repositories with a go-version line asked for stable, of which 258 either execute loong64 under qemu (180) or gate coverage at 100% (239) — the two places where an unannounced toolchain change actually bites. -from moves a pin that is already explicit, which is the job pinning created and nothing could then do: on 2026-10-09 govulncheck reported three standard-library vulnerabilities in go1.27.1 — GO-2026-6617, GO-2026-6613 and GO-2026-6611, all fixed in go1.27.2, all with reachable symbol traces — across all five go-pkgx repositories, and gopin answered 5 already explicit and stopped. -from 1.27.1 -version 1.27.2 names the version to move exactly: "move anything older" cannot tell a stale pin from a deliberate one, and this fleet has removed a deliberate loong64 pin as an outlier before, so a repository held at 1.22 is not even a candidate and the operator states what they believe is out there instead of the tool guessing. It refuses the same version on both ends, a malformed one, and a move backwards — a different act, and not one to perform across a fleet because a digit was mistyped. The report says REPIN rather than ALIAS, and the pull request is titled for the job it does: a title claiming "instead of stable" against a repository that never said stable makes a claim its reviewer can check and find false, and then nothing else in the body is worth their time. A file holding both — one job saying stable beside another naming the old version, the mixture that made stable worth replacing — gets both, the second pass reading what the first wrote. Campaign mode (v0.47.0) exists because a shell loop around gopin failed three ways on 2026-10-10, and each is now a test: --claim takes agentsync claim before editing a repository and releases it after, skipping and reporting one another session holds; a quota 403 is "wait", never a result — the account's REST budget (5000/h, shared with Renovate and other sessions) ran out while gh api rate_limit read 5000/5000, because it shows one token's bucket, so the only probe is a real call (--guard, implied by --follow), the 403 is slept out (to the advertised reset, at most --quota-wait, default 5 min) and a repository the quota never answered is deferred to a later --passes pass instead of being counted "unreadable" and done; inputs must equal outputs — every listed repository ends in exactly one counted state ("not reached" under --limit included), and a sum that differs from the list fails the run; --follow waits for the checks the way prwait does (no checks is not a pass) and merges with ghmerge when green, listing red and unreadable pull requests in the summary (one tool, not a separate gopin merge, so a claim spans edit and merge); --state FILE is a resumable repository → status record (a rerun skips finished repositories and resumes following open pull requests; it refuses a file from another --from/--version pair); an open pull request already doing the job — Renovate's "Update dependency go to vX", or an earlier run's branch — is followed and merged instead of duplicated; --gomod (with --from) also moves CI that reads its version from go.mod through go-version-file, raising a go directive that is exactly --from, running go mod tidy and committing go.mod/go.sum by name. The flags are now cobra (--from, --version, …); the old single-dash spellings (-from, -apply, …) still work. A -from pull request no longer raises go.mod, which its own body says it does not. --prefilter (v0.48.0) narrows a big inventory before the REST read: gopin costs about six REST calls a repository (workflows directory, each workflow file, go.mod) against the shared 5000/h budget, so a 2182-repository inventory would exhaust it and starve Renovate; --prefilter reads the workflow text of 20 repositories per GraphQL query (a separate budget), prints only the repositories naming something to move (an alias, the --from version, or with --gomod a go-version-file), and passes through — never drops — a repository GraphQL could not read. It also LISTS (elsewhere REPO) every repository that spells the --from version somewhere gopin does not move it — a matrix list, an env: variable, an input default — because "not a candidate" must not read as "not affected". --no-alias (v0.49.0) leaves stable aliases alone, so a security-release campaign moves only explicit pins; pinning an alias is a different change with a different review. With --gomod, --prefilter (v0.50.0) also reads the root go.mod in the same query and keeps a go-version-file repository only when its directive is exactly --from (anything else it cannot settle is passed on). Since v0.52.0 a merge made while resuming a --follow campaign is paced with --pause like an open: the merges of an already-green backlog run back to back otherwise, which is exactly the burst the SECONDARY limit brakes. --no-tidy (v0.55.0) raises a --gomod directive without go mod tidy, for modules whose replace => ../sibling directives a lone clone cannot resolve (22 of the first 170 repositories of the Go 1.27.3 campaign): raising the directive changes no requirement, and CI is the check. A go-version-file naming a path that is not in the repository (the workflow checks another repository out there — go-fleettools/fleettools reads scanner/go.mod from a scanner it clones) is skipped since v0.59.0, not reported as unreadable. A budget refusal printed by prwait or ghmerge ("GitHub's hourly budget is spent (403): it resets at …") is waited out like one from the API: on the Go 1.27.3 campaign the first --follow run recorded seven pull requests RED in two minutes on exactly that message, a verdict on the budget read as a verdict on the pull request. |
cmd/truncspill |
Where Go 1.27's loong64 back end spills a float-to-integer conversion onto a slot it can overrun -- golang/go#81000, the defect that holds go-gfx/gfx's loong64 lane at go1.26.4. It reports the stride, not the spill, because the spill alone decides nothing: go1.26.4, which PASSES on that lane, also spills twice in ./color, onto -96 and -88 -- eight bytes apart and 8-aligned, so each store fills its own slot. go1.27.1, which FAILS, spills onto -145..-140 and -276 -275, and those last two are byte-for-byte the offsets in the upstream report's own listing, which is what makes the fixture a control rather than an example. One pass over ten repositories: 329 conversions, 12 spills, 0 hazardous in shipped code; adding test files moves exactly one row, go-gfx/gfx, to 8 -- both frames landing on QuantizeUnitToByte, library code whose result only gets spilled under a test frame's register pressure. It costs no API budget at all: it reads go build -gcflags=-S output on stdin. Two of its four fixtures are mistakes it made: a character class omitting / matched nothing and reported 0 hazards on the known-failing build -- a scan that cannot read is indistinguishable from a clean one -- and counting a package compiled twice in one dump (for itself, then as a test binary's dependency) invented a stride of 0 between a spill and itself, which called int(dpi) in go-pdfkit/conformance hazardous although its slot is an 8-aligned int. A clean run means "no exposure found in THIS build", never "cannot happen": inlining is per-build, and a frame it calls hazardous may still pass, because a clobbered byte can be rewritten before anybody reads it. |
cmd/covblocks |
Counts the blocks a Go coverage profile never entered, names each one, and exits 1 if there are more than -max. It exists because the obvious instrument gives the wrong answer: go tool cover -func rounds to one decimal, so a gate written as go tool cover -func=cov.out | tail -1 | grep -q '100.0%' passes at 99.95%. Demonstrated on a real go-pkgx/bk profile — 2592 blocks, 4221 statements, nothing uncovered — with exactly one one-statement block flipped to zero: -func printed total: (statements) 100.0% and exited 0, while this named advisories.go:34.3,35.1 and exited 1. One statement in 4221 is 0.024%, so the threshold is arithmetic rather than luck: fewer than one uncovered statement per two thousand reads as a clean hundred. A note in this fleet had claimed -func said 100.0% for a profile with three uncovered blocks; re-measured, that profile (3949 statements, 4 uncovered) prints 99.9%, so the remembered number did not survive being checked and the example above is constructed to show the mechanism instead. -func has a second limit found the same day: it resolves source files through the module, so reading a profile from outside its checkout fails with "no required module provides package …" — a block count needs only the profile. It parses with golang.org/x/tools/cover rather than reading lines, because the format permits the same block twice and Go's own parser sums the counts (p.Blocks[j-1].Count += b.Count, erroring if two entries disagree about NumStmt): a line-by-line check — the awk 'NR>1 && $NF==0' idiom this replaces — would call a block uncovered on the strength of one entry while another covered it. Measured honestly: of three real profiles here (2592, 1268, 2592 blocks) none repeated a block, so that is a difference in semantics and not a bug anybody is hitting; it parses properly so as to be right by construction rather than by luck, and because a second implementation of a format is the thing that drifts. An empty profile is a failure (exit 2), because "0 uncovered" over nothing read is the same sentence as "0 uncovered" over everything, and this fleet has published a zero that meant could not read. Files are listed worst first with the name as the tie-break — and the test for that ordering was hollow twice before a mutation said so, because the file with more uncovered blocks also sorted first by name, so deleting the count comparison changed nothing. Run on itself it reports one uncovered block, main(), which os.Exit makes unenterable from a test and which is why every tool here is main() → run(); over the whole repository it reports 1235, so -max is how a lane says what it has actually earned. |
cmd/hostload |
How busy a Linux host's CPUs are now, from two readings of /proc/stat, and which are the idlest: taskset -c "$(hostload -pick 1)" ./bench.test … pins a measurement to a free CPU, and hostload -max-busy 10 refuses to start one on a loaded host (exit 1). Busy is everything but idle and I/O wait; nice and steal are shown apart. Written because the load average answered the wrong question twice: on a Loongson (cfarm401) it read 146 with every CPU idle (processes in uninterruptible sleep count toward load and use no CPU), and on a Zen 3 guest (cfarm421) a calm load hid a third of the CPU time going to other users' niced jobs. Both were found by diffing /proc/stat by hand in throwaway scripts; this is that diff with a home and tests. It runs on the remote host: cross-compile it (GOOS=linux GOARCH=riscv64 go build ./cmd/hostload) and copy it there. |
cmd/abbench |
Runs two Go test binaries alternately, base then head, for N rounds with the same arguments: abbench --base ./b.test --head ./h.test --rounds 8 --cpu 5 --max-busy 25 -- -test.run '^$' -test.bench X -test.count 1. Each run's stdout is appended to <out>/base.txt and <out>/head.txt, so benchstat base.txt head.txt compares them. It exists because two benchmark blocks run one after the other measure the host's load drift as much as the code; alternating spreads the drift over both sides. --cpu N pins every run through taskset -c N (Linux), --gomaxprocs sets GOMAXPROCS in the children (default 1, 0 leaves it as inherited), and --max-busy PCT (Linux) reads /proc/stat twice, a second apart, before each round and refuses to go on above PCT percent, with the same arithmetic as hostload (shared in internal/procstat). A child that exits non-zero stops the run (exit 1, naming the binary, the round and the child's stderr): a series with a hole in it is a biased series that benchstat would compare without a word. Usage errors exit 2. The binaries are executed from an argv, never through a shell. Pure Go, no cgo, so it cross-compiles (GOOS=linux GOARCH=ppc64le go build ./cmd/abbench) and is copied to the remote benchmark hosts like hostload. |
cmd/gvlscan |
Which calls block the OS thread without releasing the interpreter lock first — written for go-embedded-ruby/ruby, where that is an unbounded hang: a Ruby program that talked to a server in one of its own threads never returned, and the embedder's ruby.Run never got control back (#771). It exists because grep cannot answer the question: conn.Read(buf) and buf.Read(p) are the same five characters, and only one of them waits on a peer who may never send. So it loads real type information and matches a method on its receiver's type, which is what the first fixture pins — a *bytes.Reader read spelled identically to a net.Conn one, which must not be reported, because a tool that reports it has fallen back to matching text and its findings are worthless. One pass over internal/vm: 27 blocking calls, 10 wrapped, 17 not, one of which — a backtick command freezing every other Ruby Thread — was then confirmed by measurement (0.05 s against 0.61 s) and fixed. The other sixteen are candidates, not defects, and the tool says so about itself rather than leaving the reader to discover it: enclosure is lexical, so a blocking call in a helper reached only from inside a wrapper is reported anyway. That is the safe direction to be wrong in — the other one is silence about a hang — and -callers prints the sites that settle it. It refuses to report when a package does not type-check (exit 2), because an unparsed file yields no findings and reads exactly like a clean one; it prints "N scanned" beside the count for the same reason; and -kinds exists because the first real run returned 29 findings of which twelve were short internal mutex locks, among which the four unbounded network waits were invisible — so sync is off by default, and a -kinds that selects nothing is refused rather than answered with a confident zero. |
cmd/blobpurge |
Removes blobs from a repository's history by ID, into a NEW repository, and proves what the rewrite did before anybody pushes it. scan lists every blob of at least -min bytes that a branch or tag reaches and HEAD's tree no longer holds, with the tags whose trees hold it — the released trees a rewrite will change. strip pipes git fast-export --show-original-ids through a filter into git fast-import, then checks every branch and tag against the original: the same refs, the same commit count, the same authors, committers, dates and messages, and a tree equal to the old one minus exactly the stripped blobs — a tree that never held one must come out byte-identical, which is what keeps the module hash of every release made after the deletion unchanged. It never pushes. It replaces a git filter-branch --index-filter recipe whose filter was a shell script and whose only check was whatever its operator remembered afterwards. By ID because a path names every version of a file: on go-authn/bridge a 16.8 MB bridge binary sat in 35 of 36 release trees, and a later, legitimate file of that name must survive — a fixture pins that. It refuses a blob HEAD still holds (delete it in a reviewed commit first), an ID no branch or tag reaches, and an existing target. Its verification has its own break checks: a reworded message, a missing tag, a file lost without explanation and a blob still present are each fed to it and each must fail. First real run, go-authn/bridge: 1 blob, 39 refs, 1301 commits with identical metadata, 4 trees unchanged (main, both Renovate branches, v0.20.0) and 35 changed by exactly -bridge, 9.3 MiB → 2.0 MiB; it also reports the 57 commit signatures a rewrite cannot keep, counted in the source because fast-export has already dropped them by the time the filter sees the stream. A mirror cloned from a local path keeps its objects loose, so its size is loose plus packed — packs alone read 0 bytes, the first draft's mistake. |
cmd/vulnscan |
Which repositories reach a known vulnerability — govulncheck over a list of org/repo lines (-list file or stdin), each repository cloned at depth 1, scanned, and removed (-keep keeps it). Written on 2026-10-10, after thirteen standard-library advisories (fixed in go1.27.2) and five x/net HTTP/2 ones (GO-2026-6603/6610/6611/6612/6617, fixed in x/net v0.60.0) were published together and one organisation turned out to reach them in 23 of 31 repositories. Every repository gets one of four answers, and the fourth is the point: CALLED (n), CLEAN (the scan ran and reached nothing), NO-GO (no go.mod), and UNREAD — govulncheck could not load the code, which is not clean: a load failure still prints a config message and no finding, then exits 1, so a judge reading the JSON alone calls it clean. A scan is believed only with exit 0, a config and an SBOM (written only once the packages loaded); an UNREAD line carries the stderr tail and never a count. ⛔ A refusal that is recoverable must not become a verdict: a clone refused with 403 or 429 is the transport saying "come back in a moment" — measured 2026-10-31, a 58-repository sweep from one workstation got The requested URL returned error: 403 on 14 clones with nothing wrong in any of them — so such a clone is retried up to three times and the report says how often it was refused. A 404, a Repository not found or a request for a username is not retried: that repository was deleted, made private or renamed, and retrying it spends the attempts that would have read the next one. A clone still refused after the retries stays UNREAD, because unread is not clean. "Called" means a finding whose trace[0] names a function; module- and package-level findings are counted apart as required-only and imported-only. It scans every module (nested go.mod too; testdata/ and vendor/ only when they replace ../), clones the siblings a replace => ../X names — in the same organisation, or ../../other-org/X one level up, the org/repo layout these replaces are written against (without them the load fails), and runs under GOOS=linux (darwin hid nineteen Linux-only findings once; -goos adds more) and GOTOOLCHAIN from -go, default go1.27.3 — the standard library is judged by that version, so a scan whose config reports another is refused (pass go1.27.1+auto to let a module's own go line raise it). ⛔ That default follows this module's own go line, and must: on 2026-10-22 a sweep of the eleven repositories that hold the token or gate a push found GO-2026-6809 (crypto/x509, Certificate.Verify, published the day before, fixed in go1.26.10 and go1.27.3) reachable in four of them, this one included. Had the floor moved to go1.27.3 and the default stayed at go1.27.2, the scanner would have gone on judging a standard library nobody builds with — reporting a finding against repositories that had already fixed it, which is the false positive that gets a scanner ignored. Each repository also records the go-version its workflows ask for (ci_go). -json writes per-repository status, called {id, module, found, fixed}, counts and errors; -state makes a sweep resumable (-rescan, -retry-unread). -j defaults to 2 and refuses more than 3. -selftest judges three recorded govulncheck streams — a known positive, an imported-only run, a load failure — and a binary that gets one wrong says not to trust it. No GitHub REST call per repository. Exit 0 all read and clean, 1 something reachable, 2 something unread. |
cmd/vulngate |
The per-repository half of the pair above: it judges the govulncheck -format json a single repository's own CI job has just produced, in that job, against that checkout — where vulnscan asks the same question of 1800 repositories it clones. The exit status is not the gate, which is the whole reason it exists: measured 2026-10-04, govulncheck exits 0 over an advisory it has decided the module does not call (GO-2026-6443, a gRPC server that panics on a request with no :authority), so a lane reading the status is silent about exactly the finding somebody has to decide about. A reachable finding is an error; one that is not called but has a released fix is also an error, because the answer there is to upgrade rather than to carry it; anything else is a notice. It earned its keep on its first morning — 2026-10-09, thirteen standard-library advisories fixed in go1.27.2, and five in x/net of which four were reachable from go-crdt/collab's HTTP/2 transport. Three of its rules are wrong answers it or vulnscan gave first. UNREAD IS NOT CLEAN: a scan that cannot load the packages prints its config and no finding, which reads like a clean repository, so a run is believed only when it carried a config and an SBOM — the SBOM being written only once the packages loaded. An empty advisory list is not a failed scan: an earlier version refused a stream with no osv message at all, as a proxy for "it did not run", which is a false failure waiting for any module whose dependencies genuinely have nothing open against them — and the SBOM already says what the proxy was reaching for. A scan is only as fresh as its database, and nothing in the stream makes that visible unless somebody prints it, so the database's timestamp and age are on screen every run and -max-db-age (21 days, because the database is updated on business days and a tight bound fires on a quiet fortnight) refuses a frozen mirror. -go holds the scan to one toolchain, since the standard library is judged by the version the scan ran under and a lane that silently resolved another answered a different question. Validated against recorded real scans rather than only fixtures: the live crdt and collab streams pass, the same stream refuses under -max-db-age 1h, under -go go1.27.1, and with its SBOM message removed. |
cmd/needread |
The DT_NEEDED list of every ELF under a tree, from debug/elf, and it refuses to report without a positive control. It exists because "this package links no openssl" and "this program cannot read a DT_NEEDED" print identically, and the second gets read as the first. That is not hypothetical: on 2026-10-28 a sweep of a 189-project pantry with go-pkgx/bk's bk unlinked produced 140 findings, of which ten were C++ libraries reported as linking no C++ runtime — a result that is not surprising but impossible, which is the only reason anybody looked. The check had attributed libstdc++.so.6 to whichever of two projects shipping a file of that name it walked last (bk v0.44.1), and a week's worth of a second defect was hiding behind it: a real libfolly.so.0.58.0-dev refused for its non-numeric version suffix, after a libstdc++.so.6.0.33-gdb.py — a Python script — had earlier been counted as a library (v0.44.2). A reader sharing no code with the one under suspicion is what settled both, and it was only worth trusting because its control passed. Choose the control so it can fail for the right reason: the first run against gnu.org/libiconv used -must libstdc++.so.6 and failed correctly — nothing in a three-package closure consumes it, so the control measured the fixture rather than the reader; re-run with libgcc_s.so.1, which libstdc++ itself needs, it passed and the absence beneath it became evidence. Zero ELF files is a failure, not an empty answer (a wrong path, a store staged for another platform and a tree whose binaries link nothing all print the same emptiness), and a file that is not an ELF is silently skipped rather than counted. |
cmd/symfind |
Whether an ELF exports a dynamic symbol — what it provides, never what it merely calls. Undefined symbols are excluded, and that is the whole correctness of it: a library listing gettext as SHN_UNDEF is one that CALLS gettext, the exact opposite of providing it, and counting those turns "glibc provides this" into "something here mentions this", which is true of almost any library and answers nothing. Written for a claim everybody repeats and nobody checks — "glibc provides gettext itself, so nothing links -lintl on Linux". Measured 2026-10-28 against the go-pkgx registry's own glibc 2.44, with printf as the control: 2921 exported dynamic symbols, and gettext, dcgettext, dgettext, bindtextdomain, textdomain, ngettext all true. So GNU libintl is a darwin requirement, and a recipe declaring gnu.org/gettext without darwin: is dead weight for every Linux consumer — ten projects in that pantry do. A versioned symbol answers to its bare name, because gettext@@GLIBC_2.2.5 is the ordinary spelling and the caller should not have to know it. A file that exports nothing is a failed read (exit 1), not a library that provides nothing: a stripped object or a path that resolved somewhere unexpected would otherwise report false for every symbol, which is an answer, and the wrong one. |
cmd/wfaudit |
Which GitHub Actions workflows let somebody else's pull request reach this repository's secrets or write token: pull_request_target/workflow_run (HIGH when the workflow also checks out the pull request's code — the "pwn request"), write-all or a write grant at the workflow level, actions/checkout leaving its token in .git/config before the whole workspace is uploaded as an artifact, and secrets read under a privileged trigger. Rules after GitHub Security Lab's Keeping your GitHub Actions and workflows secure and zizmor's audits of the same names; template injection is left to actionlint, which already reports it. Written 2026-10-10 because the same audit done with grep in zsh read 33 unquoted paths as ONE file name and listed every workflow under every heading. It parses YAML (go.yaml.in/yaml/v3, the YAML org's maintained fork — gopkg.in/yaml.v3 is archived), refuses an empty scan and an unparsable file rather than reporting them clean, and never prints a remote URL, which can carry a credential. -api asks each repository's default token permission, one call each, and turns "no permissions block" into an answer. First run, go-widgets: 23 repositories, 31 workflows, 0 findings — confirmed by three independent counts, and against a planted pull_request_target it reports the MEDIUM. |
cmd/docsym |
Which Go identifiers a module's README names that its code does not have -- the commonest README rot a program can find: a name renamed or removed. Reads inline code spans and ```go fences, and type-checks the module plus what it requires from its own organisation under linux, darwin, windows and js (so a name only window's linux build has is found). A qualified name that resolves nowhere is MISSING, with the module version it was looked up at -- because the first real finding was window's README naming mvvm.Queue, which mvvm has had since v0.14.0 while window builds against v0.9.0 -- and, when the name is a method, the type that has it (tray.Attach → Tray.Attach). A bare name no loaded package has is only counted ("unconfirmed", -bare lists them): the first run reported 249 misses, nearly all NSEvent, MessagePort, X11 event names, so they cannot fail anything. A package that does not type-check is never judged; a README or module that cannot be read fails the run. It does NOT catch a wrong count, a wrong signature or a wrong claim about the import graph -- the three things reading found in go-widgets the day before; those still need a person. First run on go-widgets' 19 READMEs: 869 references found, 4 MISSING, all four confirmed, and fixed (window v0.89.1, application v0.8.1). ⛔ Its first version wrote into what it audited: loading under four platforms needs go.sum lines, and -mod=mod added them to the checkout's own go.sum, so the two "README only" fixes went out carrying six unreviewed go.sum lines. It now loads through -modfile on a copy; all 19 checkouts were checked unmodified before and after. A name that is right where it is gets <!-- docsym:allow NAME… -- reason -->; without a reason the allow is ignored. |
cmd/tagcells |
Keeps the version a page states for each module equal to its latest release: Markdown rows linking the repository with the tag in the next cell (go-widgets/docs' modules page, every language) and [[params.modules]] blocks in a landing's hugo.toml. Latest = highest vX.Y.Z with no prerelease, compared as semver, read with git ls-remote -- no REST budget. Reports by default (exit 1 when stale), rewrites with -write; a tag it cannot read, or a page with no cell at all, is exit 2, never "up to date". Written 2026-10-11 after the same cells were hand-edited in five files five times in two days, the first four with perl. First run: 95 cells, 19 modules, exactly the 10 stale ones it should have found, 0 after -write. ⛔ It could not read this fleet's own landings until 2026-11-01: they are [[params.repos]] with label = "vX.Y.Z", not [[params.modules]] with tag =, so against go-fleettools.github.io/hugo.toml it answered 1 file(s), 0 cell(s), 0 stale and exited 2 — right to refuse to call nothing a pass, and the reason thirteen version labels dated in a comment above them had never once been checked by the command that exists for exactly that. The key is now tag or label, and the same file then reads 11 cells, 3 stale (fleettools v0.40.1 → v0.66.0, landingkit v0.12.0 → v0.18.0, modfloor v0.6.0 → v0.6.1) — the control, on real data, that the fix sees what the blindness hid. A block now also ends at any table header and not only a [[...]] one, so a name cannot be credited a version from the next table. A label that is not a version ("no tag", "on main") still yields no cell, which is right and also means such a repository stays invisible here the day it IS tagged. |
cmd/gorequires |
Which of our modules require module X, at which version, direct or indirect -- the reverse question nothing here answered. Reads fleetfind's dated inventory and each root go.mod from raw.githubusercontent.com: plain HTTPS, no REST budget, no token (so a private repository answers 404 like a missing go.mod, and the report says so). An unreadable go.mod fails the run rather than reading as "does not require it". Written 2026-10-12 to size a go-highway downgrade (v0.0.12 requires an unpublished hwy/goat v0.0.0, ajroetker/go-highway#73): 2023 repositories, 711 go.mod, 0 unread, 47 require go-highway, 20 of them at v0.0.12 across 11 organisations -- which showed the downgrade was a cascade of releases, not an edit. |
cmd/modrename |
Gives a forked module its new module path, where it is one and nowhere else: go.mod, .go and .md files among those git tracks, never an occurrence preceded by / (a file path -- go-highway's 605 generated .s files record /Users/.../go-highway/..., and rewriting that would invent a path), never a longer name (go-highway2, .v2). Refuses a dirty tree so its diff is exactly its own; reports by default (exit 1 if anything would change), rewrites with -write; nothing to rename is exit 2. Written 2026-10-17 to fork go-highway as github.com/go-simd/highway (a replace never reaches a module's consumers): 1697 occurrences in 903 files, then 10 in 8 for go-images/jpeg2000's switch. ⛔ A consumer's renamed require keeps the old version number, which under the new path is a different module: go mod edit -require=NEW@vX before go mod tidy, or go get loads the wrong one and fails. |
cmd/i18ncheck |
Where a translated Hugo content tree has drifted from the language it was translated from: a page missing or extra, a fenced code block not byte-identical (a translated transcript is one the program never printed), a heading whose anchor differs (the translation must keep the source's as {#id}, or every link into the page loses its fragment), a relref target or a weight that differs. Anchors are computed as Hugo's default github style computes them, checked case by case against Hugo v0.167.0's own output. Written after translating go-authn's documentation into three languages with the same checks rewritten by hand each run -- until three agents' helper scripts, sharing one name in one scratch directory, overwrote each other. First run on go-fileshare's documentation: one heading in security/tls.md had lost its English anchor in all three translations. With -comments, comments may differ inside blocks whose language has them (hcl, sh, yaml, toml, ini, go): go-fileshare translates the comments of its configuration examples, deliberately; a marker inside a quoted string is a value, and a block with no language -- a program's output -- stays byte-exact. It says how many pages and blocks it compared, and exits 2 when that is none. |
cmd/langcheck |
Where our own prose on a public repository is not in English — doc comments, code comments, test case names and messages, README and documentation prose. ⛔ A grep over .go files answers the wrong question: measured 2026-10-18 across 987 local checkouts, a French word-list grep named 855 files in 25 repositories, and go-tex/engine alone accounted for 317 of them — a TeX engine's fixtures are full of French DOCUMENTS, which are data under test rather than prose we wrote. A tool that cannot tell them apart names everybody and gets ignored. So for Go it reads the comments, through go/parser — a string literal, a testdata document, a fixture holding a French sentence on purpose is not a comment and is never reported, which removes the whole false-positive class instead of filtering it afterwards. In _test.go files it ALSO reads string literals, because a case name is prose too and the first version missed every one of them. For Markdown it reads prose minus fenced blocks. ⭐ It looks for French specifically, by function words that are not also English words — plus, son, par, note, mode, point, image, format, important and patient are deliberately absent, and a test asserts an English comment using them is left alone. An accented letter counts as one signal, which is what makes a six-word case name decidable, and -min (default 2) is what stops one café in an English comment being a report; the matched words are printed so a reader can disagree. It is not a language identifier and would not notice German: the risk being measured is that the person writing it thinks in French. ⛔ It skips worktrees and .claude, and that is the COUNT and not tidiness. The first reading of go-tex/engine said 2098 French blocks in 6357 files; sampling ten of them instead of trusting the headline showed the SAME file at the SAME line ten times over, once per per-session agent worktree under .claude/worktrees/. *-worktrees was already skipped — the sibling spelling, which is what made the gap invisible. Skipping both: 549 files, 69 blocks, an inflation of 30× in the number this row was about to quote. First pass, after that fix: vm-pool-managers 708 of 1832, deskplan/web 254 of 739, go-gitsafe/gitsafe 110 of 1170 in 6 test files, …/dashboard 93, go-xrkit/desk 86, go-tex/engine 69, go-agentsync/agentsync 0 — every sampled finding genuine. A per-repository count doubles a mirror exactly as it doubles a worktree: cloud-pool-managers/vm-pool-managers and jupytercloud-project/vm_pool_manager are byte-identical at 143/1832/708. |
cmd/pinscan |
Which of our modules are pinned to an untagged commit of another of our modules that has since been released — the reverse of gorequires, which asks about one module named in advance. ⛔ It exists because go-downloader pinned go-macos/appbundle at v0.0.0-20260829193321, an untagged August commit, while appbundle.Sign shipped in v0.5.0 and the proxy serves v0.6.0: a published, documented, tested API was unreachable for seven weeks, and the compiler is what found it, on an attempt to call it. ⛔ A pseudo-version is not a defect: a module never released can only be named that way, and measured 2026-10-18 over 1003 local go.mod files, 1776 of 2017 such requires were exactly that. ⛔ And "behind" is not "different" — a sibling tool here once reported 52 stale pins by testing inequality and 35 were NEWER, so the pin's own timestamp is compared against the tag's publication time and the 8 pins ahead of their tag are reported apart, because raising those is a downgrade. That leaves the pins older than a release the consumer cannot reach: 896 of them on 2026-11-01, over 2829 local go.mod files, concentrated — go-images/images 242 consumers since 2026-06-20, go-ruby-yaml/yaml 75, go-simd/adler32 60. (An earlier figure of 233 in this row, and go-images/images at 93, was measured on 2026-10-18 over a third as many files; the tree is live and these are dated snapshots, not properties.) ⛔⛔ AND A CAMPAIGN IS COSTED IN PULL REQUESTS, NOT IN PINS, which is a factor of SEVEN. Those 896 pins sit in 123 repositories: a worktree has its own go.mod and is the same branch, and a module nested inside a checkout shares its repository's pull request. Measured in go-pdfkit, five "consumers" of go-images/images are three repositories — app with its worktree app.wt-verbs2, and html2pdf with html2pdf/corpus. go-images/images is 242 consumers in 56 repositories. So the report names both units, and the per-module table carries both, because the remedy is per module and the cost is per repository. ⛔⛔ AND MOST STALE PINS ARE NOT THE CONSUMER'S TO RAISE: 927 pins are 376 declared and 551 INHERITED (2026-11-01). go-freedesktop/notifications pins go-images/images at a 2026-08-31 pseudo-version marked // indirect, and go mod why gives notifications/cmd/notifyd → go-widgets/toolkit → go-images/images/ops — toolkit requires it directly. Raising it in the consumer treats a symptom; releasing toolkit reaches every consumer as an ordinary tagged bump, which Renovate performs by itself. That is also why Renovate had not fixed any of it, which this tool's author first blamed on dead runners: go-images's runner has succeeded daily since 2026-10-28, and Renovate raises tagged versions while the pseudo-version sits in the intermediate library. So the campaign is the 65 repositories that DECLARE a stale pin, and --repos names them. ⛔ Three classes, not two, because "unknown" is not "not ours": 60 are in a fleet org, 0 in another org, and 5 are checkouts whose origin could not be read — the first version of this counted all five as foreign, and also called wasmdesk/wasmaqua and wasmdesk/wasmbox "not ours" because wasmdesk was missing from the fleet list. A name list cannot tell not in the list from not ours. ⭐ It is a Go program and not the pipeline it replaces, and the three tests that matter are the three mistakes that pipeline made in one sitting: a proxy reply of fatal: could not read Username … sorted to the end of a version list and was reported as the latest release; v0.0.0-00010101000000-000000000000, Go's way of saying there is no commit, was read as "pinned since 0001-01-01"; and a column-aligned intermediate was re-read at the wrong offsets so the pass reported zero, which is what a clean fleet looks like too. It refuses a walk that found no go.mod for that last reason. |
cmd/unarc |
Extracts the archives in a directory, each into its own subdirectory, and never deletes the archive it read. It exists because a shell loop could not do it, for three reasons measured on a directory of 65. A command inside while read that touches stdin eats the list: unzip and 7zz do, so a loop fed by a pipe from find stopped after 3 of 62, and the list goes through a file here. A filename holding an accented character is stored decomposed on macOS, so a name quoted in composed form does not match it — zipinfo answers "cannot find or open" about a name that looks right, while os.ReadDir hands over the exact bytes. And the names inside an archive have their own encoding: APFS refuses a filename that is not valid UTF-8 with EILSEQ, and three archives carried a lone 0xE9 for é, which is Latin-1 and not the CP437 the zip specification names for an entry whose UTF-8 flag is clear (é is 0x82 there), so decoding them as CP437 would give a different wrong name; they are re-encoded and the count is reported, because a renamed file is a file the caller cannot find by the name they read in the archive. An archive's names are a trust boundary: filepath.Join neutralises a leading slash and not .., so every entry is checked against the destination after cleaning — the only form that holds — and an escape is refused and named rather than stripped, before anything is written, since a refusal halfway through leaves a directory that looks extracted. Link entries are left out, a link being the other half of the traversal. zip, tar, tar.gz, tar.bz2 and tar.xz are read; 7z, rar and tar.zst are reported as needing an external tool in their own line and their own total, because a count that mixes "already done" with "cannot do" reads as success — xz was in that list until three of the four archives it covered turned out to be .tar.xz, so the capability was added rather than reported, with a positive witness. Measured on the real directory: 62 of 63 extracted, 0 refused, 0 failed, all 62 archives still in place. |
cmd/brief |
Runs a command, shows the end of what it said, and keeps its exit status. It exists because cmd | head throws the status away and no amount of knowing that has stopped it: a pipeline's status is its last command's, and head always succeeds — false | head -3; echo $? prints 0. That is specified, not a quirk of one shell: POSIX XCU §2.9.2 Pipelines says the status "shall be the exit status of the last command that is executed in the list", and notes that an implementation may provide a way to derive it "from the exit statuses of all of the commands in the pipeline, not just the last" — which is what PIPESTATUS and set -o pipefail are, and the word is may, so leaning on them means knowing which shell is running. That has cost real answers here. Three times in one day a version tag went onto a commit that did not contain the merge, because the chain was ghmerge 21 | tail -2 && git tag …: the refusal printed, the status vanished, and a published tag does not move (that is why ghrelease exists). On 2026-10-11 it happened three more times in one session, twice on this fleet's own binaries — once reading "exit 0" for a usage error that in fact exits 2, and once reading "actionlint is not installed" from a run where actionlint had worked and reported. So this is not a convenience, it is the convenient path made correct. It exits with the child's status, which a display wrapper would forget: brief -- false && next does not run next, where false | head && next does, so every && chain built on it is as right as the command. The status goes to stderr and the command's output to stdout, so a caller may still capture the output without the status line landing in it. What is not shown is counted (… 9 of 12 line(s) not shown), because a tool that truncates in silence teaches its reader the output was complete; -head shows the first lines instead of the last, the default being the last because that is where a test suite says what failed. A signal is not a number: a command killed by SIGKILL has no exit status, and reporting 137 invites arithmetic on something that is not a count, so it is named — killed by signal: killed. Its own test runs the failing pipeline for real as a control, rather than describing it, because the claim is about the shell and not about Go. It earned its place within a minute of being built: run over this repository's suite it showed a FAIL that a | tail -3 had been hiding — this very row was missing, and TestTheReadmeNamesEveryCommand was saying so. |
cmd/wasmsize |
Where the bytes of a Go WebAssembly binary go: each section's size, then the code of each package or module, largest first, from the module's own code section. It exists because go tool nm and go version -m both answer "unrecognized file format" for a .wasm, and the question was real: go-embedded-ruby's browser build was 102 MB (21.8 MB gzipped), six times the go-ndarray playground that loads it, to run a 0.63 MB NDArray binding. Measured on it: code 60.2 MB, data 39.6 MB (no function owns that, so cutting code is not the whole story), and of the code, internal/vm 14.2 MB and the standard library 13.1 MB, then a long tail of 157 go-ruby-* gem modules and their libraries. Function names come from the name section, which cmd/link writes sanitized — every byte outside [0-9A-Za-z_.] becomes _, so github.com/go-ruby-rails/rails.(*App).Run arrives as github.com_go_ruby_rails_rails._App_.Run and cannot be split back into a path. So it matches names against the sanitized form of the build's own package list (-deps, from go list -deps), longest prefix first, and two paths that sanitize alike are reported, never guessed between. Give go list the module column (-f '{{.ImportPath}} {{with .Module}}{{.Path}}{{end}}') for -by module; without it modules are guessed from three path elements, which splits google.golang.org/protobuf in pieces — that guess is how the first run got it wrong. A build with -ldflags=-s has no names and is refused with that reason, rather than reported as all <unattributed>. Every truncation of a module is refused unless it falls between sections (then it is a valid, shorter module), and every byte overwritten with five values never panics. |
cmd/relverify |
Whether a tagged release is what its tag says: relverify go-authn/authnd v0.10.2. Waits for the tag's workflow runs, downloads the release into the user cache directory (refused inside a git work tree), and holds every file to SHA256SUMS (a file published but not listed, or listed but not published, fails), to gh attestation verify -R <repo>, and — for a Go binary, of any GOOS — to its own build information read with debug/buildinfo: module version = the tag, vcs.revision = the tag's commit (an annotated tag is followed), vcs.modified=false. Archives (.tar.gz, .tgz, .zip, as goreleaser publishes them) are opened in memory and every Go binary inside is held to the same checks, by its name in the archive — until v0.16.0 an archive release was "verified" from its sums and attestations without a single build info read, which is what it reported for go-fileshare/fileshare v0.30.0; a member over 512 MiB, or a stream expanding past 4 GiB, is refused. ⛔ Toolchain and known vulnerabilities (v0.23.0): go-authn/authnd v0.11.1 passed every check above on 2026-10-25 while all six binaries were built with go1.27.2, which GO-2026-6809 (crypto/x509) affects — relverify looked at where a binary came from, never at what it was made with. Each Go binary's toolchain (buildinfo.GoVersion) is now always printed, --min-go go1.27.3 fails an older one (compared with go/version.Compare, so go1.27.10 > go1.27.3; a devel build is refused), and every Go binary, bare or inside an archive, is scanned against the Go vulnerability database the way govulncheck -mode=binary does — through golang.org/x/vuln/scan, which runs govulncheck in-process at the version go.mod pins, rather than whatever govulncheck is on PATH. A vulnerability whose symbol is present in a binary fails the file; --allow-vuln GO-YYYY-NNNN (repeatable) accepts one by name and still prints it; --no-vuln skips the scan and says so. Binary mode cannot see a call graph, so a finding means present, not reachable, and the run prints that caveat once. ⛔ A stripped binary is judged by its source. The first version failed authnd v0.12.0 on GO-2026-5932 (golang.org/x/crypto/openpgp) and I read it as "openpgp linked whole"; it was not linked at all (go mod why: not needed). authnd releases with -ldflags="-s -w", like most of the fleet, and on a binary with no symbol table govulncheck silently falls back to module precision — every vulnerable symbol of every affected module version counts as present (x/vuln v1.9.0 internal/vulncheck/binary.go:107-111), with nothing in its JSON to say so. relverify therefore asks govulncheck's extract mode (internal/scan/extract.go) whether the binary has symbols, and for a stripped one holds each finding to a source-mode scan of the module at the tag (go mod download <module>@<tag>, the binary's own main package, its GOOS/GOARCH, and GOVERSION = the binary's toolchain, so the standard library judged is the one released): reachable FAILS; not reachable is printed as mod … module-level only and does not fail; a source scan that cannot run is exit 2. The database is fetched from https://vuln.go.dev on every run, with no cache (--vuln-db names a mirror or a file:// directory); unreachable, it is exit 2, "could not be checked", never "verified". Measured: authnd v0.11.1 FAILS on GO-2026-6809 (reachable in source: go1.27.2), v0.12.0 and v0.12.1 verify, with GO-2026-5932 reported as module-level only. The flags are POSIX --long now (cobra); the v0.22.0 spellings -wait, -dir, -norun still work for one release, with a note. Last, it runs this machine's binary with --version (read out of its archive when there is one, written under its base name alone), only once that binary has passed every other check — the vulnerability scan included: scan first, run last. ⛔ The v belongs to the tag, not to the version, and until 2026-10-20 this check demanded it: go-pkgx/pkgx v0.23.2 passed sum, attestation and build info on all twelve files and was then reported FAILED on pkgs --version: "pkgs 0.23.2" — output that is correct by the rule its own release workflow enforces (${TAG#v}), while go-pkgx/bk prints the v. A verifier that insists on one of two ordinary spellings is borrowing a convention rather than checking a fact, and a tool that cries wolf about a correct release is one nobody runs. It now accepts either — as a word, which is the other half: the check it replaced was strings.Contains(line, tag), and "0.23.25" contains "0.23.2", so accepting the bare spelling without matching a word would have traded a tool that cries wolf for one that nods along. Both directions are pinned by mutation. Each run downloads into a directory of its own and moves the finished result to the cache path: two runs on one release used to share it, and the second emptied the first mid-check. Exit 0 verified, 1 a check failed, 2 it could not be checked. It replaced a zsh function that, on 2026-10-11, waited on a release.yml go-fileshare/fileshare does not have and printed nothing for an hour; now a tag no workflow ran for is an answer after three minutes, and a GitHub release that carries notes but no files says so. |
cmd/buildtag |
ANDs a build constraint into Go files: buildtag -and '!rbgo_core' a.go b.go …. An existing //go:build !(js && wasm) becomes !(js && wasm) && !rbgo_core (parenthesised by go/build/constraint, so an OR keeps its parentheses); a file with none gets the line after its licence header, or at the top when a comment there is the package doc comment, always followed by a blank line; a file that already carries the conjunct is left alone, so a second run changes nothing. Written for go-embedded-ruby's rbgo_core build, after wasmsize showed that not calling a gem's register function drops almost nothing: the linker keeps every imported package's init, and everything that init reaches, so google.golang.org/protobuf stayed at 2.5 MB in a build that never registered Protobuf. Only a file that is not compiled stops importing — 288 files there, an edit for a tool, not for hands or a stream editor. It checks every file before writing any, and refuses — writing nothing — a legacy // +build line (gofmt keeps the pair in step; adding to one splits them), two //go:build lines, a /* */ comment before the package clause, or a result go/parser cannot read. -n lists what would change. |
cmd/mantracheck |
Whether organisations bear out the fleet's mantra, LICENSE=BSD-3-Clause and CGO_ENABLED=0, before a landing page prints it (the landing kit shows it on every landing that uses it): mantracheck go-slides go-fft go-macos. The licence is GitHub's detection: NOASSERTION or none is reported, never taken for BSD-3-Clause. It is judged on repositories that hold Go code; one without code (the organisation's .github, its brand) carries no claim and is listed apart, not counted. cgo is read from each file tree, with no source fetched: C, C++, Objective-C and Swift sources, and Go files named for cgo, outside testdata/ and vendor/. Those are signals for a person to read, printed with the files that raised them (-v for all of them). A tree read whole with no signal has no C sources to link; a truncated tree is UNREAD, never clean; an unreadable organisation is said and does not hide the next. Sources under benchmarks/ or in a nested module, and those of a repository with no Go code at all (a separate C or Swift app), are listed apart and not counted: cgo is part of a Go build, so a repository with no Go has nothing for it to compile. A Swift app beside Go code is still a signal, and rightly so: nano-container-linux/openpubkey-agent's Go module imports darwinkit and builds only with cgo. First pass, 2026-10-23, over the 17 organisations whose landing uses the kit plus six others: no cgo anywhere. The only signals were reference C benchmarks (go-fft's FFTW, lzfse's Apple implementation), standalone programs with no import "C". The real exceptions: nine landing repositories with Go in them and no LICENSE file, and go-opentype/fonts, whose code is BSD-3-Clause and whose bundled fonts keep their OFL-1.1 and Bitstream Vera licences. Data under another licence (fonts, icons, themes, macros) is listed on a data line: every licence text in a tree besides its root LICENSE, outside vendor/ and node_modules/. It is not a failure, because the code's licence still holds, but the landing's strip must name the data in a chip of its own (landingkit v0.6.0). The files are listed for a person to open: a testdata/ font is not shipped, and a NOTICE may only credit the Go authors. Exit 1 when an organisation does not bear the mantra out or could not be read, 2 on a usage error. |
cmd/landingmigrate |
Moves an organisation's landing onto the landing kit (go-fleettools/landingkit), one repository at a time, and proves what changed. inventory --out FILE classifies every organisation's <org>.github.io as kit, template (the self-contained first-generation layout), copied, no-landing or archived. It uses GraphQL, fifteen organisations a query, because GraphQL has its own budget and so does not draw down the REST budget Renovate shares. An earlier file's recorded migrations are kept, and mark ORG URL records one. migrate DIR --org O --kit vX [--data "fonts: OFL-1.1"] parses the template layout by its markers and refuses anything it does not know rather than dropping it. It then writes the kit's layout around the same content: the header becomes the hero, the first code snippet the hero's terminal (or go get lines for the repositories when there is none), and <main> stays as written under its own rules scoped to .legacy. It also removes the copies of the kit's partials, adds go.mod, kit.go and the hugo.toml import, reads [params.brand] from the logo with landingkit brand, adds actions/setup-go to the workflow that installs hugo, and puts data-licence chips in the strip. A pill, badge, sentence or footer segment that says only LICENSE=BSD-3-Clause or CGO_ENABLED=0 repeats the strip, so it is wrapped in {{ if not (partial "mantra-on.html" $) }}: hidden while the strip shows, back if the strip is turned off. One that says more is kept and listed for review. So is one holding a Hugo action, whose rendered text this cannot read. verify --old --on --off --report builds nothing itself. It reads the old page and the new page twice (strip on, strip off) and requires two things: every text node, link and picture of the old page is in the strip-off page (the licence badge excepted), and the strip-on page is the strip-off page minus exactly the hidden repeats. The second is checked by walking the two as character streams, because text alone cannot say which CGO_ENABLED=0 was hidden, the pill or the one in a kept sentence (go-macos has both). repeats LAYOUT --write applies the same hiding to a landing already on the kit. First run, 2026-10-23: 359 organisations: 30 kit, 259 template, 42 copied, 28 without a landing; wave 1 (go-opentype, go-icons, go-crdt, go-xrkit, go-macos) verified on all five. |
cmd/buildscan |
Which build configurations a repository's CI compiles, and what source none of them reaches: buildscan -repo go-crdt/collab, buildscan -dir ~/w/tree, buildscan -vet to stop asking and compile. It reads every //go:build line and every _goos_goarch.go name through go/build's MatchFile, and every go test|build|vet|install|run step in .github/workflows, with the strategy matrix expanded. A file behind a constraint is not compiled by a run that does not select it, so every statement a native go test makes about it is a statement about a configuration the file is not in — the single-configuration blind spot the product-line literature names, where Kästner et al.'s variability-aware analysis type-checks all configurations of Busybox's 811 compile-time options without the brute-force product. Two failures here, days apart and in opposite directions: go-crdt/collab's new root test file had no constraint at all while the helpers it calls are !js, so go test ./... passed on this machine and the js/wasm lane failed to compile (undefined: serve) — -vet finds it in 47 s where the lane answers in eight minutes; and go-compressions/blake3's blake3_simd_arm64.go sits behind goexperiment.simd && arm64 with the experiment set only on an amd64 job and the arm64 workflow headed "no GOEXPERIMENT", so each half is covered and the conjunction never is — run today it still names that file, and that file does not build. Measured on 2026-10-18 over 649 checkouts: 96 with something to decide about, 237 files and 36 modules that no configuration compiles, 7 with no workflows at all, and 2 with no workflows and no origin — counted apart, because nothing can run CI for a tree nobody can fetch. The figure is dated because the population moves: one repository gained its first workflow between two runs three minutes apart, which is what a 10 turning into a 9 looked like until the two lists were diffed. ⛔ It also says which branch it read, because it reads the working tree and 62 of those 649 were not on their default branch — a finding there may be somebody else's work in progress, and it stays silent for a checkout that is on its default branch and clean. That is not a nicety: seventeen checkouts chosen by a finding were once measured without asking, six were off main, and three readings were wrong because two nested modules looked fine on branches that had already been fixed. The same mistake shows what a selected sample does to a rate — 6 of 17 is 35%, and the population's rate is 9.6%. Getting there cost eight false answers, each now a test: configurations were applied to the whole tree rather than to the module the step runs in (two of the first three findings were invented submodules); go run was not counted as compiling, so cd tools && go run ./covergate read as nobody building tools/; a matrix of include: alone left one unsubstituted pass complaining about ${{ matrix.os }}; //go:build ignore means never compiled and reporting it was 19 of 76 file findings; go test -race sets the race tag; and cgo has three answers — fixing it on makes !cgo files read as compiled by nobody and fixing it off does the reverse, so a workflow that does not say gets both contexts (42 files on one checkout turned on that); and the whole block was read as one configuration, so a step holding GOOS=darwin GOARCH=arm64 go vet ./... and the amd64 line under it declared one — a limitation a workflow in another repository was reshaped to work around before it was fixed here, and fixing it needed two more: a cd on its own line applies to the commands under it, and (cd x && go run .) in a subshell is where a loop body compiles. Those last two were found by running the old binary against the new one over the same 647 checkouts and diffing the module findings — 252 files and 38 modules became 238 and 36, where the totals alone said "+2 modules" and named nothing. Its own total counts repositories, not sections (three hits++ reported 91 over fewer repositories than that), and what it cannot resolve — a GOOS from outside the file, a self-hosted label whose platform the label does not give, a step handed to a script — is printed but not counted, because mixing the instrument's limits into the finding count makes the number mean neither. It reads the working tree only and makes no API calls, so it cannot starve Renovate; it also does not ask whether a checkout is ours, which is why -repo is the mode for a decision. |
cmd/cronscan |
Whether an organisation's scheduled workflows actually fire: cronscan go-pkgx --days 14 reads the cron expressions out of each active workflow, counts the firings they ask for, and counts the schedule-triggered runs GitHub recorded. It exists because every other scanner here reads what did happen — redscan reads failed runs, prscan open pull requests, wfaudit a workflow's permissions — and none of them can see an absence: a cron that never fires is not red, not logged, and a capability nobody invokes is a capability the fleet does not have. ⛔ The listing lies by truncation. On 2026-10-19 I concluded go-pkgx's schedules were half dead because gh run list --limit 6 on its versioncheck showed six runs, all push or pull_request, with no schedule among them; it was a busy afternoon of pushes and the scheduled runs were further down. Counting needs the whole list and a denominator, and a glance gives neither. ⛔ And the denominator must respect the workflow's age: asking how many times a daily cron fired in 14 days answers 14 for a file that has existed for one, so the first version printed want 14 got 0 for three jobs merged the previous evening beside want 14 got 14 for the established ones — an organisation that reads as half broken because the instrument was wrong. The window's floor is whichever is later, the span asked for or the workflow's own created_at, and the control in the test is that an old workflow keeps the full window, or the fix would quietly excuse every real shortfall. ⛔ And its ceiling must respect the delay, which the first real run proved within a minute of the floor being fixed: at 07:18 UTC on 2026-10-20 it reported 3 of 52 firing(s) did not happen, and all three were one-day-old daily jobs whose morning run was 1h41m overdue on a service measured 8 hours late — pending, not missed. A scanner that is red every morning on a healthy fleet is a scanner nobody reads, so the window ends at now minus --grace (default 9h), and runs recorded inside the grace window still count: it errs toward believing the fleet, because the other direction cries wolf daily. What it measured, go-pkgx over 14 days with that ceiling: 47 firings asked for, 49 recorded, exit 0 — the schedules are reliable, and they arrive hours late. A 37 5 * * * job fired at 11:13 UTC and a Monday 0 5 * * 1 at 12:56 UTC, so "my cron should have run by now" is wrong before about 14:00 UTC and somebody checking the Actions tab over morning coffee concludes the job is dead. The two rows reading want 13 got 14 are that grace being honest about itself rather than hiding the arithmetic. The firing count is minute-by-minute arithmetic over the window rather than an "every N days" shortcut, because a counter that is wrong reports a crisis and an all-clear with the same confidence — and it is pinned against eleven hand-computed cases, among them cron's two spellings of Sunday (0 and 7, the second of which Go's time.Weekday does not have, so dropping it would under-report every Sunday job as a missed firing). A commented-out # - cron: is how this fleet disables a schedule and is not counted. A run that finds no cron at all says that in words and exits 0: "100%" of nothing and "every schedule fired" are the same string to a reader and opposite facts. Exit 1 when fewer firings were recorded than asked for, 2 on a usage error. |
cmd/gofloor |
Which Go toolchain each repository of an organisation can be built with, and whether CI would notice a vulnerable one: gofloor [--floor 1.27.3] ORG... reads, from the default branch through the REST API (one tree call and one contents call per file, no GraphQL), every go.mod (nested modules included), every actions/setup-go step and every govulncheck step. A go line below --floor is a finding; so is a toolchain that is not go-version-file (a hard-coded version drifts from go.mod, stable is a channel that moves with no commit); so is a repository with no govulncheck step. ⛔ A govulncheck step is not a gate: -format json, sarif or openvex make it exit 0 whatever it found (that is why vulngate exists), || true and continue-on-error (on the step or the job) do the same, and -mode binary or extract does not scan the source — each is reported as "present, gates nothing", and a step that runs vulngate counts as the judge. Written 2026-10-31 after go-authn/directory turned out to declare go 1.27.1 (CI on 1.27.2 in five jobs, no vulnerability scan): the library could be built with a standard library carrying three known vulnerabilities, and 14 of 19 sibling repositories were the same; a nested module (saml/interop/crewjam) had its own go line and broke CI when the others moved. ⛔ An unreadable repository is never clean: a failed tree or file read, a truncated tree listing (GitHub caps it, and a go.mod beyond the cut would read as absent), a workflow that is not YAML, an organisation that cannot be listed, or a sweep that found no Go module at all, exits 2, and 2 outranks 1. The planted fixture in its tests has go 1.27.1, a hard-coded 1.27.2 and a continue-on-error govulncheck step, and all three must be flagged. Exit 0 clean, 1 findings, 2 something could not be read. |
cmd/clonescan |
Which local clones can be deleted without losing work: clonescan [--depth 2] [--bundle DIR] [--recent 1h] DIR… finds every repository under each DIR and reports SAFE or KEEP with the reasons: uncommitted paths (untracked included), stashes, per branch the commits on no remote-tracking ref, detached commits on no branch, the linked worktrees, and how long ago HEAD last moved. It is read-only (git runs with GIT_OPTIONAL_LOCKS=0, so git status does not even refresh an index, and core.fsmonitor=false, so a repository's configuration starts nothing). It replaces a throwaway program that swept stale clones on 2026-10-18 and 2026-10-20, and keeps its three lessons. ⛔ A squash merge makes "unpushed" meaningless: once GitHub deletes a squash-merged branch its commits are on no remote, and the work is safe in main. A branch is therefore safe when its tip is reachable from a remote-tracking ref or is the head of a MERGED pull request whose headRefOid equals the local tip (asked through gh pr list --state merged, only for branches with unpushed commits). A tip that moved after the merge is KEEP, N commit(s) after merged PR #n, and GitHub unreachable is KEEP too. ⛔ Three "clones" were worktrees: their .git is a file holding gitdir: …/.git/worktrees/<name> (gitrepository-layout(5)), and their metadata lives in the main repository, so deleting the main first destroyed it. A worktree found on its own is reported under its main repository, from git worktree list --porcelain -z, and judged there; a dirty worktree makes the main KEEP; a main with any linked worktree hanging off it is never SAFE; a worktree whose main is already gone is KEEP (git cannot read it), never skipped. A HEAD that moved within --recent is KEEP, because something may be working in it. --bundle DIR writes one git bundle create --branches per repository, outside every work tree (refused, after walking up, before anything is written), and keeps it only once git bundle verify accepts it and list-heads equals the repository's branches; a bundle holds commits only, not stashes or uncommitted files. ⛔ There is no deletion flag, deliberately. The scan reads a repository at one instant, a peer session can commit into it the next, and a deletion cannot be undone, so deleting stays a human act on a verdict the person has read. Paths are resolved through symlinks, because git reports real paths (/private/var on macOS) and a worktree spelled through /var would not match its own record. Exit 0 all SAFE, 1 at least one KEEP or a bundle that failed its check, 2 a usage error. |
cmd/docscheck |
Which documentation generator each published entry point was built with, and whether a site's latest/ is the same site as its main/: docscheck https://go-pkgx.github.io/docs …. ⛔ It exists because I withdrew a correct bug report on bad evidence. The question was whether /docs/latest/ was the old MkDocs site or the new Hextra one, and I answered it from a file listing — assets/, 404.html, search/, sitemap.xml.gz — concluded Hextra, and closed the report. mkdocs-material produces every one of those. Shared artefacts cannot separate two candidates; only a marker exactly one of them emits can, and those are there to be had: mkdocs-material namespaces its classes md- (md-header, md-nav), Hextra carries its own name (hextra, hx-). On the very page the listing got wrong: 66 mkdocs-material markers and 0 Hextra ones under latest/, against 0 and 22 under main/. ⛔ And no status code says it — both URLs answer 200, which is why no check in the fleet had seen it. A site whose entry point is built by a different generator from its tip is not "a version behind", it is the superseded site still being served: on go-pkgx, /docs/latest/finding-packages/ tells a reader to type pkgx ls, which since pkgx#113 runs GNU ls, so the published documentation contradicts the shipped tool rather than merely lagging it. Measured 2026-10-20 over four sites: go-pkgx, go-images and go-filesystems each serve mkdocs-material at latest/ and Hextra at main/; go-plot serves Hextra at latest/ and has no main/, and it is the one that was migrated by hand as the reference for the others. ⛔ That fourth site is why the comparison is narrow: an HTTP 404, a timeout and a redirect stub are each a real answer and none of them is a generator, so comparing them as if they were reports the one site that is right as the one that is wrong — two pages are compared only when both named a generator, the rest are printed and skipped, and the tally counts what was compared. A run in which nothing was comparable is exit 2, never a clean 0: this fleet has published a zero that meant could not read. A page matching both marker sets is reported as BOTH(...) and never compared, because that means the exclusivity this whole command rests on has stopped holding, and --counts prints the evidence so a reader can disagree with a verdict instead of taking the label. pagespublish is not at fault for any of it: it points latest at "the newest release, or main when none", and a leftover mike-era 0.1/ directory is a release as far as a directory listing can tell — what is left over is the pre-migration tree sitting beside the new one, so the fix is a sweep step that removes or re-points it. Both of the claims above are pinned against mutations: widening the Hextra markers to include assets/, and comparing a 404 as a generator, are each caught. |
cmd/qemufix |
Repairs the CI lanes that run Go tests under qemu user-mode emulation, broken fleet-wide when ubuntu-latest became Ubuntu 26.04 on 2026-10-19: there qemu-user-static is a virtual package with two providers and no installation candidate, so the step exits 100 in about twenty seconds. 333 repositories in 258 organisations carried the old line. It installs qemu-user qemu-user-binfmt and accepts either qemu-<arch>-static or qemu-<arch> at run time. ⚠ Installing a provider is NOT the fix: qemu-user-binfmt registers binfmt handlers, it does not ship the binaries the lane invokes, so a lane given only a provider takes its command -v branch and goes green while testing nothing — worse than red. ⛔ So a missing emulator fails the lane, and the lane prints which emulator it ran (running ppc64le under qemu-ppc64le): without that line, "ran under emulation" and "skipped" are indistinguishable from a green tick, which is how a fix for this looks like it worked. It recognises seven shapes and refuses by name rather than guessing — a half-patched file, two install steps, a multi-line install it would have to replace wholesale, a run: that is not the step's last key, two lanes of different shapes — because 333 files cannot be reviewed one by one and the only safe failure is a loud one. ⛔ The brief for the sweep said there were three shapes; reading the files found a fourth (retry+case, a literal emulator per case arm), a third of the first batch, which a tool that guessed would have mangled silently. ⛔⛔ Three more came out of the thirty files the first version refused, and each refusal said something false about the lane. 17 were reported as having no go test -exec= step, read as "these lanes never emulate" — the opposite of true: twelve go-macos repositories run a cross-built pkg.test under "$QEMU" (oneline+qemuvar) and five go-pkgx ones spell the flag with a space, -exec ${{ matrix.qemu }} (guard+pkgxenv, which resolves in the already-guarded install step and crosses to the test step through $GITHUB_ENV). 11 were reported as a step with no run: key: the line it had read was a matrix datum, exec: "-exec=qemu-riscv64-static", and the - { … } item around it is not a step — a command lives at or after its step's run:, and oneline+matrixflag reads the whole flag from the matrix. ⚠ That last shape carries the campaign's sharpest trap: the same job holds native lanes with exec: "" (386 executes directly on an x86-64 runner, deliberately — qemu-i386 was measured to lose the guest's floating-point state across Go's preemption signal), so the rewrite branches on the empty string instead of failing a lane that is correct. Its tests assert the three invariants per shape, with a control that runs the invariant checkers against the unfixed input so three checkers that pass on everything cannot pass the suite, and a planted positive: an already-fixed file comes back byte-identical and reported as already-fixed, which is what makes a second pass over 333 repositories a no-op instead of 333 empty commits. |
scripts/tidyall.sh |
Runs go mod tidy on a PR branch and pushes only if it changed something AND the tree still builds. |
scripts/pinapply.sh |
Applies what gopin reports, by CLONING and pushing with gitpush rather than through the contents API — because gopin -apply cannot write a workflow file with the token gh holds. Measured on go-avkit/bitstream: the GET succeeds, the POST that creates the branch succeeds (so the token does write to that repository), and only the PUT under .github/workflows/ fails — as a bare {"message":"Not Found","status":"404"} naming neither the scope nor the path. gh api -i user gives admin:public_key, gist, read:org, repo and no workflow. ⚠ ghscopes does NOT show this: with no -f it reads ~/.github-token, the WIDE token, so running it with and without -f returns two identical answers about one file and looks like confirmation. The alternative fix is the one wfmerge.sh names — gh auth refresh -s workflow, once — which makes gopin -apply work directly; this script needs no scope change because git-credential-tannevaled serves the wide token to git over a pipe. It reads gopin's own report, so a line saying go.mod HELD leaves go.mod alone, and it claims each repository through agentsync and skips what another session holds. |
scripts/wfmerge.sh |
Refuses by default. It lands workflow-touching PRs by pushing to the default branch, which git-pre-push-guard exists to stop. The fix is gh auth refresh -s workflow, once. |