Fix every false positive, false negative, warning and error in CI/CD - #291
Conversation
Adding .gitkeep for PR creation (default mode). This file will be removed when the task is complete. Issue: #290
setuptools>=77 warns about the deprecated "License :: Public Domain" classifier and about the license table, both removed on 2027-02-18. Use the SPDX expression and license-files instead, and add python/LICENSE so the wheel actually carries the text that license-files points at. python -m build now completes with zero warnings.
…rmat maven-compiler-plugin warned "system modules path not set in conjunction with -source 11" because source/target only change the language level, not the API the code is checked against. maven.compiler.release does both. Spotless runs google-java-format inside the Maven JVM, which on JDK 17+ has no access to jdk.compiler internals and prints a reflective-access warning per module. .mvn/jvm.config opens exactly the packages it needs.
The format job ran "npx prettier --check .", which downloads whatever version is newest at that moment, so a Prettier release could turn a green branch red without a commit. Pin it as a devDependency and add format/format:check scripts so local runs and CI use the same formatter.
Link<T>.Values is declared nullable, so every use of it outside a null check produced CS8602 or CS8604. In the library the two affected methods now take a nullable string and materialise Values once; in the tests the existing Values! convention is applied to the remaining call sites. dotnet build now reports 0 warnings and all 196 tests still pass.
The workflow fetched docfx.json, filter.yml, toc.yml and four shell scripts from the main branch of linksplatform/Scripts at run time and then patched them with about a dozen sed calls, so the build depended on an unpinned third-party branch while the NuGet key was in scope, and bugs in those files could not be fixed here. Vendoring them fixes three of those bugs directly: - toc.yml pointed at api/Platform.$REPOSITORY_NAME.html. The name is wrong and so is the form: DocFX resolves homepage against the *source* file, so the two InvalidFileLink warnings only disappear with obj/api/<name>.yml. - docfx.json targeted netstandard2.0 and branch master. - publish-docs.sh cloned github.com/linksplatform/links-notation and pushed gh-pages there. That only worked because GitHub still redirects the old location to link-foundation/links-notation. PackageIconUrl is removed: it is deprecated (NU5048) and the URL it named returns 404. experiments/cref-repro records the one warning that remains, which comes from the Pegasus code generator. DocFX now builds with 1 warning instead of 3.
…eal push The workflow downloaded docfx.json, filter.yml, toc.yml and four shell scripts from the main branch of linksplatform/Scripts at run time and then patched them with about a dozen sed calls. That made every run depend on an unpinned third-party branch while the NuGet API key was in scope, and it meant bugs in those files could not be fixed from this repository. The vendored copies added in 14beb21 replace all of it. Other defects fixed here: - pushToNuget, generatePdfWithCode and publishDocumentation declared `needs: [test]` but their `if:` read `needs.findChangedCsFiles.outputs.isCsFilesChanged`. A `needs` reference to a job that is not in `needs` evaluates to empty, so the condition was always false and the three jobs never ran; the workflow reported green while shipping nothing. - publishRelease was gated on `needs.pushToNuget.result == 'success'`. A job whose publish step is skipped still reports success, so a missing API key produced a green run and a GitHub release for a package that was never pushed. It is now gated on an explicit `published` output that is only 'true' after `dotnet nuget push` has run and the version has been observed on api.nuget.org. - The release tag was `${VERSION}_csharp`; every other language uses `<language>_<version>`. - publishRelease, generatePdfWithCode and publishDocumentation had no `timeout-minutes`. - No `permissions:` block, so every job got the default write token. The workflow is now read-only except for the two jobs that write to the repository. - No `concurrency:` groups. gh-pages deploys and NuGet pushes can no longer race; check jobs cancel superseded runs on branches but never on main. Adds an opt-in `verbose` dispatch input (default false, also readable from the CI_VERBOSE repository variable) so the next failure can be diagnosed without editing the workflow, and reports credential presence — never the value — before pushing.
…ipped `publishRelease` was gated on `needs.publishToNpm.result == 'success'`, and a job whose publish step is skipped still reports success. Run 33150292364 is the result: the publish step failed with ENEEDAUTH because NPM_TOKEN was empty, nothing reached the registry, and the run still went green. - publishToNpm now exposes a `published` output that is only 'true' after `npm publish` succeeded and the version was observed on registry.npmjs.org; publishRelease gates on that instead of the job result. - Authentication moves to npm trusted publishing (OIDC, `id-token: write`, npm >= 11.5.1 installed explicitly), with NODE_AUTH_TOKEN as the bootstrap fallback. Writing the token into ~/.npmrc with `echo` is gone: it put the secret in a file on disk and produced the empty-token failure silently. - A rejected credential now fails the job with a message that says which of the two mechanisms to configure, instead of being swallowed. - `npx prettier --check .` resolved an unpinned prettier on every run and ran without dependencies installed; it now uses the pinned devDependency via `bun run format:check`. - `bun install` -> `bun install --frozen-lockfile` so a stale bun.lock fails instead of being silently rewritten. - Adds least-privilege `permissions:`, per-job `concurrency:` groups, and the opt-in `verbose` dispatch input (default false).
Publishing:
- `cargo publish --token ${{ secrets.CARGO_TOKEN }}` passed the token as an
argv element, where any process on the runner could read it from /proc.
It now goes through rust-lang/crates-io-auth-action (RFC 3691 trusted
publishing) with CARGO_REGISTRY_TOKEN as the transport and the long-lived
CARGO_TOKEN kept only as a bootstrap fallback.
- publishRelease was gated on `needs.publishToCratesIO.result == 'success'`,
which is also what a job that published nothing reports. It now gates on a
`published` output that is 'true' only after cargo published the crate and
the version was observed on crates.io.
- A missing credential warned-and-skipped invisibly; it now emits a warning
annotation and blocks the release.
- `--allow-dirty` is gone: it suppressed exactly the "you are shipping files
that are not in the commit" check that publishing needs.
- The macro crate is published first, and the run waits for it to appear in
the index before publishing the crate that depends on it, instead of
racing crates.io propagation.
Caching: three hand-rolled actions/cache steps per job are replaced by
Swatinem/rust-cache. The old keys were `${{ runner.os }}-cargo-*-${{
hashFiles('**/Cargo.lock') }}` with no restore-keys, so every lockfile change
was a 100% miss rather than a partial restore, and the `target` key did not
include the compiler version, so a toolchain bump reused stale artifacts.
Also adds least-privilege `permissions:`, per-job `concurrency:` groups and
the opt-in `verbose` dispatch input (default false).
- `pip index versions <pkg> | grep -q "$VERSION"` matched substrings, so a version was considered published as soon as any longer version string contained it (0.1.0 looks published once 0.1.0.1 exists) and the release was silently skipped. Replaced with an exact lookup against https://pypi.org/pypi/<name>/<version>/json. - The version was also parsed by `grep "^version"` on pyproject.toml in publishRelease while publishToPyPI used tomli, so the two jobs could disagree. Both now read the version once, in publishToPyPI, and publishRelease consumes it as a job output. - `twine upload` with TWINE_PASSWORD is replaced by pypa/gh-action-pypi-publish with `id-token: write`, so PyPI trusted publishing is used when configured and PYPI_TOKEN stays as the bootstrap fallback. `packages-dir: python/dist` is explicit because `defaults.run.working-directory` does not apply to action inputs. - publishRelease gated on `result == 'success'`, which a job that published nothing also reports; it now gates on the `published` output, which is only set after the upload succeeded and the version was observed on PyPI. - `twine check dist/*` runs in the test job, so malformed metadata fails on pull requests rather than at upload time. Also adds least-privilege `permissions:`, per-job `concurrency:` groups and the opt-in `verbose` dispatch input (default false).
Same rewrite the other four languages received: - `publishToPackagist` exposes an explicit `published` output (`true` / `skipped` / `failed`) instead of reusing `should_publish`, which was true whenever the version merely was not on Packagist yet, and `publishRelease` gates on that output instead of on `needs.publishToPackagist.result == 'success'` — a job result that is also `success` when the publish step did nothing. - The version check now distinguishes "the package is not on Packagist at all" from "this version is not there yet". It is the first case today: both https://repo.packagist.org/p2/link-foundation/links-notation.json and https://packagist.org/packages/link-foundation/links-notation.json return 404. `api/update-package` only re-crawls an already-submitted package, so the PHP publish step has never published anything and cannot until someone submits the package once at https://packagist.org/packages/submit. The workflow now says so with a `::warning::` instead of exiting 0 in silence. - Missing `PACKAGIST_USERNAME` / `PACKAGIST_TOKEN` is reported the same way rather than passing empty credentials to the API. - After a successful update call the version is verified against repo.packagist.org (Packagist crawls asynchronously) before any GitHub release is created. - Adds `workflow_dispatch` with a `verbose` input, `permissions: contents: read` at the top with `contents: write` only on `publishRelease`, `CI_VERBOSE`, per-job concurrency groups, and the quoted `ALL_CHANGED_FILES` change detection.
- `publishToMavenCentral` exposes an explicit `published` output
(`true` / `skipped` / `failed`). The previous output was
`have_creds == 'true' && should_publish == 'true'`, which is decided
before `mvn deploy` runs, so it claimed a publish for a version that
the Portal may never have accepted.
- Maven Central has no OIDC trusted publishing, so the Portal token and
GPG key stay. They are now read through `env:` instead of being
interpolated into the script body with `${{ secrets.* }}`, which put
the values into the generated shell script.
- `autoPublish` uploads a bundle that the Portal validates and syncs
asynchronously, so a green `mvn deploy` is not a published artifact.
A new step polls repo1.maven.org for the .pom (up to 15 minutes)
before `publishRelease` may run, and `publishRelease` gates on the
`published` output instead of `result == 'success'`.
- `publishRelease` no longer installs a JDK and shells out to
`mvn help:evaluate` three times to re-derive coordinates the publish
job already emitted as outputs.
- Adds `workflow_dispatch` with a `verbose` input, top-level
`permissions: contents: read` with `contents: write` only on
`publishRelease`, per-job concurrency groups, quoted
`ALL_CHANGED_FILES` change detection, and `fail-fast: false` on the
JDK matrix so one JDK version failing still reports the others.
Go has no registry to push to — the module proxy serves whatever the `go/vX.Y.Z` tag points at — so creating the tag is the publish. The release job now reads the version and module path into outputs, reports `published` as `true`/`skipped`, and then polls `proxy.golang.org/<module>/@v/<version>.info`. That both proves the release is consumable and warms the proxy for the first `go get`, which otherwise pays the fetch cost. Also: `workflow_dispatch` with a `verbose` input, top-level `permissions: contents: read` with `contents: write` only on `publishRelease`, per-job concurrency groups, a `publish-go` group that does not cancel in progress, quoted `ALL_CHANGED_FILES` change detection, a non-empty check on `go/VERSION` (an empty file used to produce the tag `go/v`), and the redundant JDK-style Setup Go step dropped from a job that runs no Go commands.
`workflow_dispatch` was declared but every job was gated on `isDocsFilesChanged`, which is derived from a changed-file list that is empty for a dispatch — so the manual trigger silently did nothing. It now counts as changed, the same way the language workflows handle it. Other changes: - `pages: write` and `id-token: write` were granted to the whole workflow, including the job that checks out the repository and runs `npm ci` on third-party code. They now live only on `deploy`. - The site is built on pull requests as well, so a broken build is caught in review; upload and deploy still only happen off main. - `actions/upload-pages-artifact` v4 -> v5 (v5.0.0 is current). - Node 20 -> 22, matching the version the other workflows use. - `timeout-minutes` on all three jobs; none had one, so a hung `npm ci` could occupy a runner for the six-hour default. - Quoted `ALL_CHANGED_FILES` change detection. Verified `npm ci && npm run build` in docs/website still succeeds.
auto-merge: - `contents: write` + `pull-requests: write` were granted at the workflow level, so they applied even to the runs that immediately no-op because the author is not Dependabot. Moved onto the job. - On a `pull_request` event raised by Dependabot, GITHUB_TOKEN is read-only regardless of the `permissions:` block, so `gh pr merge --auto` gets an HTTP 403. The workflow now prefers `secrets.DEPENDABOT_AUTO_MERGE_TOKEN` and, when that secret is absent, says so with a `::warning::` instead of failing with an unexplained 403. - Adds `timeout-minutes` and a per-pull-request concurrency group. bom-check: - Adds `permissions: contents: read` (it had none, so it ran with the repository default), a concurrency group, `workflow_dispatch`, and an `::error::` annotation so a BOM shows up in the run summary rather than only in the step log.
Adds a `workflows` workflow that runs actionlint and zizmor over `.github/workflows/` on every change to `.github/`. Nothing checked the workflows themselves before, which is how csharp.yml shipped three jobs whose `if:` referenced a `needs.` job they did not depend on — the expression evaluated to empty, the jobs never ran, and every run was green. actionlint reports exactly that. zizmor's remaining findings drove two changes: - `artipacked` (41 occurrences): `actions/checkout` leaves the GITHUB_TOKEN in `.git/config` by default, so every subsequent step — including `npm ci`, `cargo build` and the DocFX scripts, which all execute third-party code — could read it. Every checkout now sets `persist-credentials: false`. The only step that pushes, csharp/scripts/publish-docs.sh, already authenticates with an explicit `x-access-token:` URL, so nothing depended on the persisted credential. - `adhoc-packages`: `npm install -g npm@latest` is pinned to `npm@11`. Node 22 bundles npm 10.x and trusted publishing needs >= 11.5.1, so the upgrade stays, but a future npm 12 cannot change publish behaviour without a commit here. `.github/zizmor.yml` relaxes `unpinned-uses` from the default blanket hash-pin policy to `ref-pin` (tag or SHA, never a mutable branch), matching how the link-foundation pipeline templates pin actions and how Dependabot keeps them current. The CI job fails at medium severity and above; with that config the whole tree is clean.
Adds scripts/release-audit.mjs and a workflow that runs it on every push to main, weekly, and on demand. It reads the version each language declares in its source tree and compares it with what that language's registry actually serves. Its first run is the evidence for this pull request: js: declared 0.15.0, latest on npm is 0.13.0 csharp: declared 0.15.0, latest on NuGet.org is 0.13.0 java: declared 0.3.0, nothing published on Maven Central php: declared 0.2.0, nothing published on Packagist python, rust, go: in sync Every one of those had green CI. The release jobs gated on `needs.<publishJob>.result == 'success'`, which is also what a job reports when its publish step was skipped, so a missing credential or — in csharp.yml's case — an `if:` referencing a job that was not in `needs:` looked identical to a successful release. Drift is reported as annotations, not as a failure: a version bump legitimately lands before the release that publishes it.
… exist Every go run logged, twice: ##[warning]Restore cache failed: Some specified paths were not resolved, unable to cache dependencies. `cache-dependency-path: go/go.sum` points at a file that is not in the repository — the module has no dependencies at all, go.mod has no require block — so setup-go could resolve neither the hash input nor GOMODCACHE. Turning the dependency cache off removes both warnings and the pointless cache round-trip. If the module ever gains dependencies, the go.sum that appears alongside them is the signal to turn it back on.
`publishRelease` depended on `publishDocumentation`, so a DocFX or gh-pages failure suppressed the GitHub release for a package that was already on NuGet.org — the release then never appeared at all, because the next run sees the version as published and skips. The release now depends only on the NuGet push it is reporting.
Java declared 0.3.0 and PHP declared 0.2.0 while JavaScript, Python, Rust, C# and Go all declare 0.15.0. The two lagged only because their publish jobs have never released anything — `scripts/release-audit.mjs` confirms Maven Central and Packagist hold nothing at all for this project — so no published version is being skipped and the numbers are free to move. Aligning them means every implementation of the same grammar carries the same version, which is what the single shared CHANGELOG already assumes, and it lets the release audit report one number instead of three.
…path The generator read rust/tests, which the Cargo workspace move turned into a non-existent directory, so it crashed with ENOENT and the pre-commit hook that runs it failed every commit touching a test file. The document had been stale ever since: 137 Python tests recorded against 146 today. The per-test links also used a `file:line` suffix, which GitHub reads as part of the path, so all 827 of them 404'd. They now use `#L<line>`.
lychee reports every Markdown link in the repository. The three genuine failures it found are fixed here: the root READMEs pointed at rust/README.md, which the workspace move turned into rust/links-notation/README.md, and the CHANGELOG linked releases under a `vX.Y.Z` tag scheme this repository has never used. The C# API reference URLs stay in .lycheeignore until the first Pages deployment that includes the gh-pages overlay; npmjs.com and stackoverflow.com reject data-centre traffic and would only ever be false positives.
…able
csharp/scripts/publish-docs.sh pushes the DocFX site to the gh-pages branch,
and the branch does contain csharp/api/*.html. GitHub Pages, however, serves
the artifact this workflow uploads ("build_type": "workflow"), so every
/csharp/ URL the READMEs advertise returned 404 while CI stayed green.
The build now overlays the gh-pages content onto the uploaded site, and a
workflow_run trigger rebuilds the site when the csharp workflow publishes new
documentation.
CodeQL over every language with an extractor (PHP has none; php.yml runs PHPStan instead), dependency review on pull requests, and an npm audit of both committed lockfiles. The audit already had something to say: js/package-lock carried brace-expansion 5.0.8, vulnerable to GHSA-mh99-v99m-4gvg, while js/bun.lock had moved on to 5.0.9.
Principle 11 of the best-practices document. --results=verified reports only credentials the issuing service confirms are still live, so the job can fail the build without drowning it in false positives.
…rejects
The previous reproduction only demonstrated the documentation-ID form that
works. Probing every shape shows the Pegasus output is invalid for a wider
range than assumed: nesting breaks it, and so does a single language keyword
(`IList{string}`), while the one shape that does bind silently resolves to
the open generic. Reported upstream as otac0n/Pegasus#137.
A local run over `./**/*.md` picked up 4271 links, 3400 of them from js/node_modules and docs/website/node_modules, and ~400 of those were dead links inside third-party package READMEs we do not control. Nothing installs dependencies in this job today, so this is a guard against that changing rather than a fix for a current failure. With the exclusions the repository's own Markdown checks clean: 1050 total, 0 errors.
`dev/log/` is gitignored (.gitignore carries a `[Ll]og/` pattern), so the raw material stays local: 3.3 MB of downloaded CI logs and 376 KB of cloned pipeline templates. What is tracked here is the written analysis drawn from them, force-added so it survives the ignore rule. README.md what is here and in what order to read it analysis/REQUIREMENTS.md every requirement in the issue, with status analysis/TIMELINE.md how the two failure sequences unfolded analysis/ROOT-CAUSES.md each defect traced to its cause and fix analysis/BEST-PRACTICES-COMPLIANCE.md the 13 hive-mind principles, checked analysis/CI-CD-BEST-PRACTICES.md the upstream text, pinned as read analysis/UPSTREAM.md the issues filed against other projects analysis/PRIOR-ART.md existing tools surveyed before writing any analysis/*.txt, *.json the measurements the prose cites
Every change this pull request makes to the pipelines, grouped the way the rest of the file is: what was added, what changed, and what was broken and is now fixed.
main carried seven Dependabot bumps that touch the same workflow lines this branch rewrote: actions/checkout v6 to v7, setup-dotnet v5 to v6, setup-node v6 to v7, upload-pages-artifact v4 to v5, fetch-metadata v2 to v3, and xunit.runner.visualstudio 3.1.5 to 4.0.0. Every conflict was main's version number against this branch's surrounding change, so the resolution keeps both: this branch's content on the new action versions. The bumps were also applied to the workflows added here, which main has never seen, so no v6 checkout is left anywhere. actionlint exits clean and zizmor reports no findings.
|
You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool. What Enabling Code Scanning Means:
For more information about GitHub Code Scanning, check out the documentation. |
The workflows check added in this branch went red on its first run, which is the check working: actionlint's Docker image bundles shellcheck, so it lints every `run:` block, and 60 of the findings were the same one — `>> $GITHUB_OUTPUT` unquoted. That is harmless on GitHub's runners today because the path has no spaces, but it is a real hazard in a script that writes a job's contract, so all of them are quoted now. The other two: java.yml wrote three outputs through three separate redirects (SC2129), now one grouped redirect; and bom-check.yml printed the BOM byte escapes through `echo`, which is allowed to expand them (SC2028), now `printf '%s\n'`, so the advice it prints stays literal. It went unnoticed locally because a native actionlint without shellcheck on PATH skips the shell checks and exits 0. That trap is now written down next to the step.
The secret scan failed on its first run with
trufflehog: error: flag 'no-update' cannot be repeated, try --help
The action's own wrapper already passes --no-update, so repeating it in
extra_args aborted the scan before it read a single commit. Removed, with
a note next to the input so it is not added back.
The action ref pinned the wrapper but not the scanner: the `version`
input defaults to `latest`, so the run pulled whatever image that tag
pointed at. It is now pinned to 3.97.1 — the image tag carries no `v`
prefix, unlike the action ref, which a local `docker run` confirmed
(`manifest unknown` for v3.97.1, `trufflehog 3.97.1` without it).
Verified locally with the exact command the action builds:
6381 chunks, 13.7 MB, verified_secrets 0, exit 0.
The job-level concurrency group left out the matrix value, so all four PHP versions shared one group and cancelled each other. Two consecutive runs on this branch show it: 8.1, 8.2 and 8.3 were cancelled at 09:01:30Z, the same second they started, having run no steps, while 8.4 completed. The repository has been testing one PHP version, not four. java.yml and security.yml already key their groups on the matrix value; php.yml was the only one that did not.
An annotation sweep over all 61 jobs of the 12 green runs on 31ec6aa returned one notice and nothing else, so the logs were downloaded in full and grepped instead. Three findings only existed in step output: - codecov has answered `Upload queued for processing failed: {"message": "Token required - not valid tokenless upload"}` on every run. The repository has no CODECOV_TOKEN secret, and `fail_ci_if_error: false` turned that permanent failure into a green step. The upload now runs only when the secret exists and fails the job when it does; without it the job says so with a ::notice:: instead of pretending. - the same step also found and uploaded experiments/test_coverage_data.json under the `go` flag. `files:` narrows what is passed as --file but does not disable the CLI's workspace search, so `disable_search: true` was needed. - the website build warned on every run that vite.config.js is ESM loaded as CommonJS. `"type": "module"` in docs/website/package.json fixes it; npm ci && npm run build now completes with no warning. The investigation log gains Class F in ROOT-CAUSES.md, covering these three and the three defects the new checks caught on their first run (the 62 shellcheck findings and the local actionlint that missed them, the trufflehog flag repetition, and the php matrix self-cancellation), plus a third sequence in TIMELINE.md.
The gate added in 2537067 used codecov-action@v6 and read the secret directly. Both templates that already solved this problem (csharp #34, python #27, both closed) settled on the same shape: promote the secret to job-level env, gate the step on `env.CODECOV_TOKEN != ''`, pass it as `token: ${{ env.CODECOV_TOKEN }}`, and let a real failure fail the job. python #34 (also closed) moved off the deprecated @v4; @v7 is current. Also record the cross-template audit that this branch's work produced. All seven link-foundation/<lang>-ai-driven-development-pipeline-template repositories were linted with actionlint 1.7.7 and shellcheck, giving 23 findings and one meta-finding that explains them: no template runs actionlint or zizmor anywhere in .github. Seven issues are filed, each with evidence, a reproduction, a workaround and a diff: rust #141 printf in single quotes, ${DOCKERHUB_IMAGE} never expands csharp #49 ${{ github.head_ref }} interpolated into a run: body python #62 unsupported `queue: max` concurrency key go #6 Codecov upload that can never succeed, hidden twice java #6 Codecov fail_ci_if_error: false, @v4, v3-era file: input js #145 no workflow linter; SC2046, SC2034 php #3 no workflow linter (otherwise clean) Five of the seven had already been fixed elsewhere in the same template family, so each report cites its closed precedent. experiments/upstream-templates/rust-manifest-printf-quoting.sh reproduces rust #141 end to end. Refs #290
All 13 logs downloaded and grepped in full rather than read through annotations: zero ##[warning], zero ##[error], and the only two ##[notice] lines are intentional. F3-F6 are each confirmed gone at their source - all four PHP legs complete, the coverage step is skipped with a spoken notice instead of a silent failed upload, and the Vite CommonJS warning is absent. Refs #290
Working session summaryDone. PR #291 is ready for review: #291 Upstream reports filed — all seven templates now covered, each with quoted evidence, a reproduction, a workaround and a diff:
Five of the seven cite an already-closed precedent from a sibling template (rust#113/js#117, csharp#34/python#27, python#34, js#115), so they land as missed sweeps rather than opinions. The meta-finding is that no template runs Verification on the final head
That distinction mattered: the previous annotation-only sweep returned one benign notice while three real defects (a coverage upload that had never succeeded and was reported as success, an unrelated file uploaded under the Working tree is clean, This summary was automatically extracted from the AI working session output. |
🤖 Solution Draft LogThis log file contains the complete execution trace of the AI solution draft process. 💰 Cost: $40.858039📊 Context and tokens usage:Claude Opus 5: (10 sub-sessions)
Total: (25.4K new + 931.7K cache writes + 41.9M cache reads) input tokens, 414.9K output tokens, $40.786983 cost Claude Haiku 4.5:
Total: 33.9K input tokens, 1.4K output tokens, $0.071057 cost 🤖 Models used:
📎 Log file uploaded as Gist (12673KB)Now working session is ended, feel free to review and add any feedback on the solution draft. |
🎉 Auto-mergedThis pull request has been automatically merged by hive-mind.
Auto-merged by hive-mind with --auto-merge flag |
Closes #290.
What was wrong
Both default-branch runs named in the issue failed, and neither was an isolated
accident:
csharp33150292349 — the publish step pushed the package to NuGet, then thedocumentation step failed, and the workflow reported failure for a release
that had actually shipped. The run also carried 56 nullable reference
warnings and a set of invalid
crefvalues in generated code.js33150292364 — the publish step failed on credentials while the jobaround it succeeded, so a version that never reached npm was recorded as
released.
Auditing outward from those two runs turned up eighteen further defects across
every workflow in the repository, including two that nobody had reported: the
C# API reference had been published to
gh-pagesfor months but wasunreachable, because Pages serves the uploaded artefact and not that branch;
and the pre-commit hook that regenerates
TEST_CASE_COMPARISON.mdhad beenfailing with
ENOENTsince the Rust workspace was restructured, so every testcount in that document was stale and every link in it was dead.
The full investigation — timeline, root cause per defect, and the evidence each
claim rests on — is committed under
dev/log/issues/290/pulls/291/.Start with its
README.md.What changed
Releases now tell the truth. Every publish step verifies the artefact after
pushing it: npm, PyPI, crates.io, NuGet, Maven Central, Packagist and
proxy.golang.org are each polled for the version just released, and the GitHub
release is created only once the registry confirms it. The gate is an explicit
publishedoutput rather thanresult == 'success', which is also true for ajob that published nothing.
scripts/release-audit.mjsruns on a schedule andcompares every declared version against what the registries actually serve, so
a silent failure cannot stay silent.
Credentials. npm, PyPI and crates.io publish through OIDC trusted
publishing where it is configured, falling back to the existing token secret
until it is. Checkouts no longer persist the job token; workflow
permissionsare declared least-privilege per job.
Correctness of the pipelines themselves.
workflow_dispatchdid nothing inany workflow — every job was gated on a changed-file list, which is empty for a
manual trigger. Concurrency groups cancel superseded pull request runs but never
cancel a run on
main, and publish jobs queue rather than cancel.always()was replaced by
!cancelled()where a cancelled run should not continue.New checks, so the next defect of each kind is caught in review:
workflows.github/workflows/securitylinksrelease-auditWarnings fixed rather than suppressed: all 56 C# nullable warnings, the
setup-gocache pointed at ago.sumthat does not exist, a Cargo cache keyedwithout a compiler version, setuptools deprecations, google-java-format noise,
Java compiling against the wrong API level, and Prettier resolved at run time.
Verbose mode. Every workflow declares a
verboseinput and exportsCI_VERBOSE, default off, so diagnostics can be turned on for a single runwithout committing anything.
Best practices and templates
All thirteen principles from
hive-mind's CI-CD-BEST-PRACTICES.md
were checked one by one against this repository; the result, including the four
gaps that remain and why, is in
BEST-PRACTICES-COMPLIANCE.md.All seven
*-ai-driven-development-pipeline-templaterepositories were clonedand diffed against these workflows. Where a defect fixed here also exists
upstream, it was reported:
always(), unverified publishcrefvalues the C# compiler rejectsLater, once the
workflowscheck on this branch had proved its worth (seebelow), the same
actionlint+shellchecksweep was run over all seventemplates. It produced 23 findings and one meta-finding that explains them:
no template runs
actionlintorzizmoranywhere in.github/. Seven morereports were filed, one per template, each closing with the
workflowsjob thispull request adds:
printfformat is single-quoted, so${DOCKERHUB_IMAGE}is never expanded andimagetools creategets a literal${DOCKERHUB_IMAGE}@sha256:…${{ github.head_ref }}interpolated into arun:body — script injection on fork PRs — in a step that already declares the value asenvqueue: max, which is not a validconcurrencykey and is silently ignoredfail_ci_if_error: falseandcontinue-on-error: true;@v4fail_ci_if_error: false,@v4, and the v3-erafile:inputFive of those seven had already been fixed elsewhere in the same template
family — rust#113 and js#117 for
queue: max, csharp#34 and python#27 forCodecov gating, python#34 for
@v4, js#115 for thehead_refinterpolation,all closed — so each report cites its precedent rather than arguing from taste.
Each report carries a reproducible example, a workaround and a suggested fix.
The Pegasus one is backed by
experiments/cref-repro/,which probes six
crefshapes and shows that nesting and a single languagekeyword both break, while the one shape that does bind resolves to the wrong
(open) generic; rust#141 is backed by
experiments/upstream-templates/rust-manifest-printf-quoting.sh,which reproduces the unexpanded image name in six lines.
What the new checks caught on their own first run
The
workflowscheck earned its place immediately, and in a way worthrecording, because it also exposed a false negative in how it had been verified
locally:
>> $GITHUB_OUTPUT. A localactionlinthad exited 0 over the same tree ten minutes earlier —actionlintwithoutshellcheckonPATHsilently skips everyrun:block and still exits 0. The job uses the Docker image, which bundles it.
parsing with
flag 'no-update' cannot be repeated, because the action alreadypasses that flag. The scanner version was also floating on
latestwhile thewrapper action was pinned.
phpmatrix was cancelling itself. All four legs shared oneconcurrency group, so
8.1,8.2and8.3were cancelled the same secondthey started while
8.4completed and the run reported green: the repositoryhad been testing one PHP version and reporting four.
With those fixed, all 12 runs went green and an annotation sweep over all 61
jobs returned a single benign
notice. That was not sufficient. Downloading all12 logs in full (3.1 MB) and grepping them found three more defects that no
annotation reported, because plain step output never becomes one:
Upload queued for processing failed: {"message":"Token required - not valid tokenless upload"}and the step still endedoutcome=success, becausefail_ci_if_error: false. The step is now gated on the secret's presence,fails loudly when an upload that should work does not, and says so out loud
when it is skipped.
goflag.experiments/test_coverage_data.jsonwas swept up becausefiles:adds towhat the Codecov CLI finds rather than replacing it — its own
action.ymldocuments this. Fixed with
disable_search: true.ESM syntax in a file loaded as CommonJS.docs/website/package.jsonnow declares"type": "module".The single sentence the whole investigation reduces to, in all three of its
sequences: a check that is not actually running is indistinguishable from a
check that passes.
Verification
actionlintexits 0 over all workflows — run viadocker://rhysd/actionlint:1.7.7, soshellcheckreally is onPATH;zizmor --min-severity mediumreports no findings.lycheeover the repository's Markdown: 1050 links, 0 errors (before: 566"File not found" in
TEST_CASE_COMPARISON.mdalone).experiments/cref-reproandexperiments/upstream-templates/rust-manifest-printf-quoting.shreproducetheir upstream defects on demand.
swept rather than only their annotations:
grep -h "##\[warning\]\|##\[error\]" ci-logs/*.logreturns nothing.The only two
##[notice]lines are intentional — the links workflow's summarylink, and the "CODECOV_TOKEN is not configured" line added above, which exists
precisely so that a missing upload is stated rather than disguised as a
successful one.
phpmatrix runs all four legs to completion (8.1,8.2,8.3,8.4), where three of the four were previously cancelled on every run.mainis merged in. It carried seven Dependabot bumps touching the same linesthis branch rewrote; the resolution keeps this branch's content on the new
action versions, and the bumps were applied to the workflows added here too.
Needs a maintainer
These cannot be done from a pull request. Each is a one-time console action:
@link-foundation/links-notation, orset
NPM_TOKEN— until then the publish step warns and skips.NUGET_API_KEY; the current one failed authentication.[PHP] 0.2.0release, which points at a version thatwas never published anywhere.
workflows already prefer when present.
DEPENDABOT_AUTO_MERGE_TOKEN, which is declared and unused.main, remove thelink-foundation.github.io/links-notation/csharp/entry from.lycheeignore— it is only there because the URL 404s until that deploy lands.