Repository navigation
Check Composite Action Metadata Against the GitHub Action Schema - #1686
Conversation
Implements item 4 of the answers recorded on [#1155](#1155). Items 1 to 3 are prose and land separately. ## The gap `workflow-ci-contract` "After Any Workflow Edit" sent a reader to actionlint. actionlint's own file list is `.github/workflows/*.yml` and `*.yaml`, and it opens a composite action's `action.yml` only through a workflow's `uses: ./.github/actions/<name>`, and only to judge that caller's `with:` block against the action's declared inputs. Nothing read the action file as action metadata. So a reader edited an `action.yml`, ran the prescribed check, and got a clean result that had judged only the caller's contract with it. ## Why actionlint cannot be the tool, measured rather than inferred `docker run --rm -v "$PWD":/repo -w /repo rhysd/actionlint:latest .github/actions/validate/action.yml` exits 1 with six syntax-check errors, `"jobs" section is missing in workflow`, `"on" section is missing in workflow`, and `unexpected key` for each of `description`, `inputs`, `outputs` and `runs`. It parses action metadata as a workflow, so widening its globs is not available. Measured on a scratch repository, so the corrected skill text claims no more than what actionlint does: a composite action carrying a malformed `if:` expression and an unterminated shell `if` in a `run:` block, plus a second action referenced by no workflow and missing both its top-level `description` and an input's, passes actionlint at exit 0. The same two files put to `check-jsonschema` report the two missing descriptions. ## What runs instead `check-jsonschema`'s `vendor.github-actions` builtin, which is GitHub's own action-metadata schema. It runs via `uvx`, beside the ruff and mypy invocations already in the same job, because `check-jsonschema` publishes no container image: upstream `python-jsonschema` carries no Dockerfile and Docker Hub holds only unaffiliated rebuilds. That is why `scripts/docker_lint.py`, which the recorded answer named, is unchanged: it runs each linter in a container with `--network none`, so neither a pulled image nor an in-container install is available to it. - `validate-task.yml` gains a `Lint composite actions step`, gated on the metadata existing, taking its file list from git and skipping rather than invoking on an empty argument list, matching the shell and PowerShell steps above it. It sits after the Python steps rather than beside actionlint because it needs the uv they set up. - `Setup uv step` now also fires for a repo that carries composite actions and no `pyproject.toml`, which would otherwise reach the new step with no `uvx` on the runner. - Both the guard and the file list use `**`, the guard through `hashFiles` and the list through a `:(glob)` pathspec, so the two describe one set. `hashFiles`'s `*` does not cross a `/` and git's default pathspec `*` does, so the earlier `*` form would have skipped a repo whose actions nest one level deeper while still checking a nested file whenever a flat one existed. - `OPERATIONS.md` "Local Verification" runs the same command, with the same empty guard. Without it, a checkout carrying no composite action reaches `check-jsonschema` with no instance files, which exits 2 and aborts the rest of the block under its own `set -Eeuo pipefail`. - `GOVERNANCE.md` "Running the Linters Locally" names the new check, since that block states it is the whole of `validate-task.yml`, and both host READMEs name it in their own enumeration of the lint tools that run through a container or `uvx` rather than natively. That section's own lead names it too, since it tells an agent to trust that enumeration before reporting a check unavailable. ## What the corrected skill text says "After Any Workflow Edit" now says which part of a composite action each check reaches, and what neither reads: a composite action's `run:` bodies and its `if:` expressions. It also says that where no workflow in the repository names the action, actionlint reaches it not at all, which is the ordinary shape downstream: a repo's hooks are invoked from a hub reusable workflow, and no workflow in that repo carries a `uses: ./.github/actions/...` line, so its own actionlint run never opens them. `docs/reusable-workflows.md` stated the reason wrongly in a snippet comment and is corrected to that one. The schema check's scope is stated as `.github/actions/`, matching the step's own guard, since an action file outside that tree is read by neither check. ## The producer-failure shape this repository already ruled against The step's file list is written to a file rather than into a process substitution. `.github/workflows/canonical-review-sweep.yml` states the reason in this tree already: `mapfile` reports success whatever the producer did. Measured both forms against a failing `git ls-files`: the redirect aborts the step at 128, and `mapfile < <(...)` continues with an empty array and exits 0, which would have reported "no composite action metadata is tracked" and gone green with the gate never run. Dropping `mapfile` also keeps the `OPERATIONS.md` block runnable on macOS, whose `/bin/bash` is 3.2 and has no such builtin. That block opens with `set -Eeuo pipefail`, so the failure would not have skipped one check, it would have truncated every gate below it. The list is `git ls-files -z` into `xargs -0`, since a bare `xargs` splits on whitespace and honours quotes. Measured: a tracked `.github/actions/my hook/action.yml` reaches the checker as two arguments and the run exits 123, where `-0` passes it whole and exits 0. `-0` is the form both GNU and BSD carry, where `-d` is GNU-only and would break the macOS copy. The pathspecs are `:(top,glob)` so a run from a subdirectory reads the same nine files rather than reporting a silent clean over none. ## The eight findings the new gate raised on its first run A gate that fails on the corpus it guards blocks every commit from the moment it lands, per `GOVERNANCE.md`, so the tree is brought to green in the same change. Eight inputs carried no `description`, which GitHub's schema requires: `semver2` in `docker-prepare-default` and `nuget-build-default`, and `assembly-version`, `assembly-file-version` and `assembly-informational-version` in both `dotnet-publish-default` and `nuget-build-default`. Each now describes the NBGV output it threads and the MSBuild property it sets. `dotnet-publish-default` passes its `assembly-version` as both `AssemblyVersion` and `Version`, and its `semver2` reaches `PackageVersion` alone, so those two say so rather than reading like their `nuget-build-default` counterparts, where `semver2` is `Version`. ## Verification The declared local gate set in `OPERATIONS.md` "Local Verification" passes whole, and `docker_lint.py` is clean for actionlint, markdownlint and editorconfig-checker. The new command was run at the exact form the workflow and the local block use, over all nine tracked action files, and its empty-list path was run in a scratch repository carrying none. `workflow-ci-contract` regenerates into all three distributions.
|
Warning Review limit reachedNext included review available in 59 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Repository: ptr727/ProjectTemplate/.coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: ⛔ Files ignored due to path filters (3)
📒 Files selected for processing (10)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🟡 Changes recommended
The updated workflow-ci-contract skill text contains an incorrect actionlint file-glob description that should be corrected to avoid misleading local verification guidance.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
This PR closes the CI gap where composite action metadata (.github/actions/**/action.{yml,yaml}) was not being validated as action metadata by adding a check-jsonschema schema check (via uvx) and updating docs/skill text to reflect the new verification surface.
Changes:
- Add a
uvx check-jsonschema --builtin-schema vendor.github-actionsgate for tracked composite action metadata invalidate-task.yml, plus a matching local invocation inOPERATIONS.md. - Expand
setup-uvgating so repos withoutpyproject.tomlstill getuvxwhen they carry composite actions. - Fix existing composite action metadata failures by adding missing
inputs.*.descriptionfields, and update documentation/skill distributions accordingly.
File summaries
| File | Description |
|---|---|
| OPERATIONS.md | Adds a local check-jsonschema invocation guarded on a non-empty tracked action list. |
| host-setup/windows/README.md | Updates host-tooling docs to include check-jsonschema in the “not installed natively” list. |
| host-setup/linux/README.md | Updates host-tooling docs to include check-jsonschema in the “not installed natively” list. |
| GOVERNANCE.md | Documents check-jsonschema as part of the authoritative lint set for repos carrying .github/actions/. |
| docs/reusable-workflows.md | Updates a comment describing when .github/actions/** should be included in target path filters. |
| .github/workflows/validate-task.yml | Adds a composite action metadata schema-check step; ensures setup-uv runs when composite actions exist. |
| .github/skills/workflow-ci-contract/SKILL.md | Regenerated distributed skill copy reflecting the updated workflow guidance. |
| .github/actions/nuget-build-default/action.yml | Adds required input descriptions to satisfy GitHub’s action metadata schema. |
| .github/actions/dotnet-publish-default/action.yml | Adds/clarifies required input descriptions to satisfy GitHub’s action metadata schema. |
| .github/actions/docker-prepare-default/action.yml | Adds required input description to satisfy GitHub’s action metadata schema. |
| .claude-plugin/fleet-skills/skills/workflow-ci-contract/SKILL.md | Regenerated distributed skill copy reflecting the updated workflow guidance. |
| .claude-plugin/fleet-skills/.source-digests/workflow-ci-contract | Updates the distribution digest for the regenerated workflow-ci-contract skill. |
| .agents/skills/workflow-ci-contract/SKILL.md | Updates the source skill text describing what actionlint does vs schema validation. |
Review details
- Files reviewed: 13/13 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Answers the review round on [#1686](#1686). Every claim below was measured against `rhysd/actionlint:latest` in a scratch repository rather than reasoned about, because this paragraph has now been wrong four rounds running. ## Three claims were false, one of them as the reviewer asked for it **Discovery is recursive.** The reviewer asked for `.github/workflows/*.yml` and `.github/workflows/*.yaml`, and `*` does not cross a `/`. A file at `.github/workflows/sub/nested.yml` is linted, so writing the reviewer's form would have put a fresh false statement into carried content. The text now says actionlint discovers `.github/workflows/` recursively, both extensions. **It does validate a referenced action's metadata.** Measured against a local action reached through `uses: ./.github/actions/hook`: `description is required in metadata of "H" action`. It checks `name`, the top-level `description`, and `runs.using`, reporting each against the calling workflow. "It never validates the action file as action metadata" was false. **It reads more of the caller than `with:`.** `${{ steps.h.outputs.notdeclared }}` draws `property "notdeclared" is not defined in object type {realout: string}`, so it types the caller's `steps.<id>.outputs.<name>` against the action's declared outputs too. What actionlint does not reach that way is a missing per-input `description` and an unexpected top-level key, which is what leaves the schema check something to do. The text says that rather than implying a clean split. ## The schema is not GitHub's `vendor.github-actions` is SchemaStore's vendored schema rather than a GitHub-published one, and the two diverge. `runs.using: node16` passes the schema and actionlint rejects it: `invalid runner name "node16" ... valid runners are "composite", "docker", "node20", and "node24"`. So the text no longer calls it GitHub's schema, since a green run there is not a statement about what GitHub accepts. ## Two `GOVERNANCE.md` corrections The CI bullet said "for a repo that carries a **composite** action". The guard is `hashFiles('.github/actions/**/action.yml', ...)` and the file list is the same two patterns, so any action metadata under that tree fires it whatever `runs.using` declares. `check-jsonschema` comes back out of the "not installed natively" sentence. That sentence sends a reader to this section for the working invocation and the section states none for it, so naming it there promised an answer that is not present. The CI bullet still names it and gives the `uvx` form. ## Why this is a second commit The previous commit is already pushed, and `GOVERNANCE.md` "Git and Commit Rules" says never force push, so refining it by amend is not available. The branch is append-only from its first push.
There was a problem hiding this comment.
🟡 Changes recommended
The updated canonical skill text contains a repository-inaccurate claim about actionlint workflow discovery and an unnecessary/contradictory provenance assertion about the schema source that should be corrected before merge.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (1)
.agents/skills/workflow-ci-contract/SKILL.md:33
- The skill claims actionlint discovers
.github/workflows/recursively, but this repository's actionlint invocation targets only.github/workflows/*.ymland.github/workflows/*.yaml(non-recursive), so the guidance here is misleading for nested workflow paths.
**actionlint discovers `.github/workflows/` recursively, both extensions, and opens no action file on its own.** It reaches a local action's `action.yml` only through a workflow's `uses: ./<path>`, wherever in the tree that path leads, and reports everything it finds there against the calling workflow: the caller's `with:` block against the action's declared inputs, the caller's `steps.<id>.outputs.<name>` against its declared outputs, and the action's own `name`, top-level `description` and `runs.using`. A missing per-input `description` and an unexpected top-level key are the metadata it does not reach that way. Pointing it at an action file directly makes it parse the file as a workflow and report several syntax-check errors, so widening its file list is not available. **Where no workflow in the repository names the action, actionlint reaches it not at all**, which is the ordinary shape for a repository whose hooks are invoked from a hub reusable workflow rather than from a workflow of its own.
- Files reviewed: 13/13 changed files
- Comments generated: 1
- Review effort level: Lite
|
Answering the suppressed comment on
Declining, with a measurement rather than an argument. Both halves are testable and both come out the other way. 1. actionlint's own discovery is recursive. With no file arguments, in a scratch git repository holding The nested workflow is linted. That is also the form CI runs, since the 2. This repository's own invocation is recursive too. So the declared patterns do select nested workflows, and the local runner agrees with CI rather than diverging from it. The general point worth keeping, because it has now come up twice in this review: a git pathspec is not a shell glob and not minimatch. It is load-bearing in this pull request's own new step, which is why that file list is spelled No change made for this one. If a nested workflow can be produced that either invocation misses, I will take the finding. |
There was a problem hiding this comment.
🟢 Approval recommended
The CI gating change is properly guarded, consistent with the documented local invocation, and the updated action metadata fixes bring the tree back to green under the new schema check.
Review details
- Files reviewed: 13/13 changed files
- Comments generated: 0 new
- Review effort level: Lite
Answers Copilot's second round on [#1686](#1686). The sentence asserted where the builtin schema comes from, "SchemaStore's vendored schema rather than a GitHub-published one". The origin is not what a reader of this skill needs, and it is a claim that has to be kept true as upstream moves. What a reader needs is the behaviour, which is measured rather than asserted: the schema accepts `runs.using: node16` and actionlint rejects it with `invalid runner name "node16" ... valid runners are "composite", "docker", "node20", and "node24"`, so a green schema run is not a statement about what GitHub currently accepts. That is what the sentence now says, and nothing about provenance. The round's other finding, that this repository's actionlint invocation is non-recursive, is declined on the pull request rather than here, since it opened no thread. `scripts/docker_lint.py` passes its patterns to `git ls-files` as a pathspec rather than to a shell, and git's default pathspec magic is wildmatch without `WM_PATHNAME`, so `*` matches `/` and `git ls-files -- '.github/workflows/*.yml'` returns a nested workflow.
|
@coderabbitai review |
|
There was a problem hiding this comment.
🟢 Approval recommended
The added CI and local gates correctly validate composite action metadata with appropriate guards, and the PR fixes the existing schema violations it would newly enforce.
Review details
- Files reviewed: 13/13 changed files
- Comments generated: 0 new
- Review effort level: Lite
|
Recording an execution boundary on this pull request's review coverage, rather than leaving it to be inferred from a green check. CodeRabbit did not review this pull request. Its check reports
and it has posted zero review comments here. This repository also receives no automatic CodeRabbit review at all, having fewer than ten stars, so the explicit trigger was the only route and it was refused on quota. What has read this change, so the coverage claim is exact rather than implied:
The maintainer's call was to proceed on that coverage rather than hold the change for a quota reset. Stated here so a later reader does not mistake CodeRabbit's |
|
Tip For best results, initiate chat on the files or code changes.
The ✏️ Learnings added
You are interacting with an AI system. |
…Check (#1687) Promotes the two changes that implement [#1155](#1155 recorded answers, items 1 to 3 as prose and item 4 as tooling. ## The delta Two squash commits, 21 files, read from `git diff origin/main origin/develop` rather than from a local fast-forward's file list: - `e18730f` [#1685](#1685), the carried-text defects. - `d0e722f` [#1686](#1686), the composite-action schema check. ## What changes for a carrier **`repo-worktree`** no longer forbids in one unit the command it requires in another. "The Base Branch" names the base-clone return as a second exception beside the cleanup step, with the ban rescoped to commands that mutate the working tree, index or HEAD, so `worktree remove` and `branch -d` sit outside it rather than as uncounted exceptions. "Creating a Worktree" splits the continuation by which checkout holds the branch: the base clone is returned to `develop` when its tree is clean and stopped on when it is not, while a previous session's worktree is retired rather than switched, since it normally occupies the very path `worktree add` wants. **`resync-a-repo`** carries both audit invocations itself rather than pointing at `RESYNC.md` section 2 for them, so the skill works read in isolation. `python3 spec/audit.py <RepoName>` measures the ground-truth branch the registry names, and `--branch <ref>` previews in-flight work. The invocation is spelled with `python3` at all four sites, since a shebang does not execute on Windows and this fleet declares that platform first-class. **`GOVERNANCE.md` "Repository Layout"** gains rows for `STANDUP.md`/`RESYNC.md`, `.agents/`, `host-setup/`, `.github/skills/` and `.claude-plugin/fleet-skills/`, and no longer describes this repository's CI as lint-only or its `.github/workflows/` as holding only its own workflows. **A new CI gate** validates every tracked `action.yml` under `.github/actions/` against the action-metadata schema, which actionlint cannot do because it parses an action file as a workflow. It found eight real defects on its first run, all fixed in the same change. `workflow-ci-contract` now states what each check reads and what neither reads, all of it measured rather than asserted. ## Review coverage Both pull requests reached every Merge Gate item before merging: review on head, full coverage, no refusal, zero unresolved threads and zero suppressed findings on head, checks 8 of 8. One boundary, already recorded on [#1686](#1686) and repeated here so it is not inferred from a green check: **CodeRabbit did not review that pull request.** Its check reports `pass` while its own reply to the explicit trigger reads "Review rate limited", and it posted zero comments there. It did review [#1685](#1685). Proceeding on Copilot plus the local passes there was the maintainer's call rather than an inference from the check. ## No closing references None of the issues this work touches is finished by it, so this pull request closes nothing. - **#1155** keeps items 5 to 10. Item 5 has a recorded answer and no implementation, and items 6 to 10 have no answer. - **[#1684](#1684 keeps `README.md`'s copy of the lint-only claim and the question of whether the prose term or the registry classification is the defect. - **[#1682](#1682 is unimplemented, though this round's own cleanup ran its recommended fix by hand and it worked: the remote branch delete ran from each task's worktree before that worktree was removed, which the guard permits where the base clone would have denied it. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Check Composite Action Metadata Against the GitHub Action Schema
Implements item 4 of the answers recorded on
#1155. Items 1
to 3 are prose and land separately.
The gap
workflow-ci-contract"After Any Workflow Edit" sent a reader toactionlint. actionlint's own file list is
.github/workflows/*.ymland*.yaml, and it opens a composite action'saction.ymlonly through aworkflow's
uses: ./.github/actions/<name>, and only to judge thatcaller's
with:block against the action's declared inputs. Nothingread the action file as action metadata. So a reader edited an
action.yml, ran the prescribed check, and got a clean result that hadjudged only the caller's contract with it.
Why actionlint cannot be the tool, measured rather than inferred
docker run --rm -v "$PWD":/repo -w /repo rhysd/actionlint:latest .github/actions/validate/action.ymlexits 1 with six syntax-checkerrors,
"jobs" section is missing in workflow,"on" section is missing in workflow, andunexpected keyfor each ofdescription,inputs,outputsandruns. It parses action metadata as a workflow,so widening its globs is not available.
Measured on a scratch repository, so the corrected skill text claims no
more than what actionlint does: a composite action carrying a malformed
if:expression and an unterminated shellifin arun:block, plusa second action referenced by no workflow and missing both its top-level
descriptionand an input's, passes actionlint at exit 0. The same twofiles put to
check-jsonschemareport the two missing descriptions.What runs instead
check-jsonschema'svendor.github-actionsbuiltin, which is GitHub'sown action-metadata schema. It runs via
uvx, beside the ruff and mypyinvocations already in the same job, because
check-jsonschemapublishes no container image: upstream
python-jsonschemacarries noDockerfile and Docker Hub holds only unaffiliated rebuilds. That is why
scripts/docker_lint.py, which the recorded answer named, is unchanged:it runs each linter in a container with
--network none, so neither apulled image nor an in-container install is available to it.
validate-task.ymlgains aLint composite actions step, gated onthe metadata existing, taking its file list from git and skipping
rather than invoking on an empty argument list, matching the shell and
PowerShell steps above it. It sits after the Python steps rather than
beside actionlint because it needs the uv they set up.
Setup uv stepnow also fires for a repo that carries compositeactions and no
pyproject.toml, which would otherwise reach the newstep with no
uvxon the runner.**, the guard throughhashFilesand the list through a:(glob)pathspec, so the twodescribe one set.
hashFiles's*does not cross a/and git'sdefault pathspec
*does, so the earlier*form would have skippeda repo whose actions nest one level deeper while still checking a
nested file whenever a flat one existed.
OPERATIONS.md"Local Verification" runs the same command, with thesame empty guard. Without it, a checkout carrying no composite action
reaches
check-jsonschemawith no instance files, which exits 2 andaborts the rest of the block under its own
set -Eeuo pipefail.GOVERNANCE.md"Running the Linters Locally" names the new check,since that block states it is the whole of
validate-task.yml, andboth host READMEs name it in their own enumeration of the lint tools
that run through a container or
uvxrather than natively. Thatsection's own lead names it too, since it tells an agent to trust that
enumeration before reporting a check unavailable.
What the corrected skill text says
"After Any Workflow Edit" now says which part of a composite action each
check reaches, and what neither reads: a composite action's
run:bodies and its
if:expressions. It also says that where no workflow inthe repository names the action, actionlint reaches it not at all, which
is the ordinary shape downstream: a repo's hooks are invoked from a hub
reusable workflow, and no workflow in that repo carries a
uses: ./.github/actions/...line, so its own actionlint run neveropens them.
docs/reusable-workflows.mdstated the reason wrongly in asnippet comment and is corrected to that one.
The schema check's scope is stated as
.github/actions/, matching thestep's own guard, since an action file outside that tree is read by
neither check.
The producer-failure shape this repository already ruled against
The step's file list is written to a file rather than into a process
substitution.
.github/workflows/canonical-review-sweep.ymlstates thereason in this tree already:
mapfilereports success whatever theproducer did. Measured both forms against a failing
git ls-files: theredirect aborts the step at 128, and
mapfile < <(...)continues withan empty array and exits 0, which would have reported "no composite
action metadata is tracked" and gone green with the gate never run.
Dropping
mapfilealso keeps theOPERATIONS.mdblock runnable onmacOS, whose
/bin/bashis 3.2 and has no such builtin. That blockopens with
set -Eeuo pipefail, so the failure would not have skippedone check, it would have truncated every gate below it.
The list is
git ls-files -zintoxargs -0, since a barexargssplits on whitespace and honours quotes. Measured: a tracked
.github/actions/my hook/action.ymlreaches the checker as twoarguments and the run exits 123, where
-0passes it whole and exits 0.-0is the form both GNU and BSD carry, where-dis GNU-only andwould break the macOS copy. The pathspecs are
:(top,glob)so a runfrom a subdirectory reads the same nine files rather than reporting a
silent clean over none.
The eight findings the new gate raised on its first run
A gate that fails on the corpus it guards blocks every commit from the
moment it lands, per
GOVERNANCE.md, so the tree is brought to green inthe same change. Eight inputs carried no
description, which GitHub'sschema requires:
semver2indocker-prepare-defaultandnuget-build-default, andassembly-version,assembly-file-versionandassembly-informational-versionin bothdotnet-publish-defaultandnuget-build-default. Each now describesthe NBGV output it threads and the MSBuild property it sets.
dotnet-publish-defaultpasses itsassembly-versionas bothAssemblyVersionandVersion, and itssemver2reachesPackageVersionalone, so those two say so rather than reading liketheir
nuget-build-defaultcounterparts, wheresemver2isVersion.Verification
The declared local gate set in
OPERATIONS.md"Local Verification"passes whole, and
docker_lint.pyis clean for actionlint,markdownlint and editorconfig-checker. The new command was run at the
exact form the workflow and the local block use, over all nine tracked
action files, and its empty-list path was run in a scratch repository
carrying none.
workflow-ci-contractregenerates into all threedistributions.
Local review
Three adversarial passes, 22 findings, every one dispositioned. The first pass disproved this change's own central claim: the draft said actionlint reads no action file at all, and it does, opening a local action through a workflow's
uses: ./<path>to judge that caller'swith:block. Correcting the premise did not invalidate the gate, since nothing before this reached the action file's own metadata, but the justification was rewritten around what actionlint actually does.Each later pass found a defect in the previous pass's fix. Round 2 replaced
mapfileto stop a producer failure reading as an empty list, and introduced anxargsquoting regression in doing so. Round 3 settled both withgit ls-files -zintoxargs -0.Fleet blast radius
Every
action.ymlthe fleet carries outside this repository was fetched and checked against the same builtin:LanguageTags,aiopurpleair,Financial-Modeling,HomeAutomation-Config,ESPHome-ConfigandBlogall pass, so no repository reds on its next pin bump.BlogandHomeAutomation-Configcarry composite actions and nopyproject.toml, so both newly runsetup-uvand oneuvxresolve in their lint job once they bump. That cost is intended rather than incidental.Known stale, deliberately untouched
validate-task.yml's file header anddocs/reusable-workflows.mdboth enumerate the lint job's checks and neither names this one. Both are comment lines, whichprose_lint.py'scomment-addedrule refuses on a change that edits them unless the pull request says comments are wanted. Left for that decision rather than edited here.🤖 Generated with Claude Code