Skip to content

ci: block releases whose action.yml cannot be loaded - #7

Open
SafeEval wants to merge 1 commit into
mainfrom
agent/release-guardrail
Open

ci: block releases whose action.yml cannot be loaded#7
SafeEval wants to merge 1 commit into
mainfrom
agent/release-guardrail

Conversation

@SafeEval

Copy link
Copy Markdown
Member

First: the reported v1 issue is already fixed

The other session's follow-up said v1 still points at 84abbef with the broken action.yml. That was true when it was written, but is stale now. Verified:

v1      -> 202ec098c184db396450308dff91596643c52fdd   (== v1.1.0)
v1.0.0  -> 84abbef298fc3ea70c0978ee0aa30ed28f1ee8f7   (broken, kept as history)

v1 action.yml PARSES OK

v1 was moved by hand earlier, and the release workflow now moves it automatically on every release. @v1 resolves to working code. No action needed there.

The real gap: two holes in the existing guard

The release job had a YAML parse check, but:

1. The immutable tag was pushed before anything validated it. The version job tagged and pushed with no check; only the alias move was guarded. A tag is public and permanent the instant it exists — validating afterwards can't recall it. That's why v1.0.0 will always name a broken action.

2. Parsing isn't loadability. A file can be valid YAML and still be rejected by GitHub. All of these parse fine and are unusable:

name: x
description: y          # no runs: at all
runs:
  using: composite
  steps:
    - run: echo hi      # run: without shell:

The guardrail

.github/validate-action.py checks the structure GitHub actually requires — name/description/runs, runs.using, non-empty composite steps, every step having uses: or run:, run: steps having shell:, and inputs being well-formed mappings with descriptions. It reports every problem, not just the first.

Wired at three points, each strictly before a tag write:

Where Guards against
CI, every PR broken action.yml reaching main
version job, before git push origin $next an immutable broken tag existing at all
release job, before the alias moves @v1 ever pointing at something unloadable

I verified the ordering programmatically rather than by reading — validation precedes the tag push and the alias move in both jobs.

Tests

12 cases, both directions, run in CI — a guardrail that can't fail isn't one:

  • Catches the exact dedented-heredoc shape that shipped as v1.0.0, plus missing runs:, run: without shell:, empty steps, steps with neither uses: nor run:, runs: without using:, inputs missing descriptions, non-mapping documents, and unreadable files
  • Accepts a valid minimal action and one using a uses: step

Confirmed against the real artifacts: current action.yml passes, v1.0.0's fails with not valid YAML: mapping values are not allowed here.

actionlint clean; all three YAML files parse.

Note

This is a ci: commit, so merging deliberately cuts no release — the guardrail lands without a version bump. The next fix:/feat: will be the first release to pass through it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VSrrciEDBTuFNMtKocScML

v1.0.0 and, until recently, v1 both named an action.yml that did not parse,
so the action could not be loaded by anyone referencing them. The release
job gained a YAML parse check, but two gaps remained: the immutable tag was
pushed by the version job before anything validated it, and parsing alone
does not prove an action is usable — a file can be valid YAML and still lack
runs:, or carry a run: step with no shell:, either of which makes GitHub
reject the whole action.

Add validate-action.py, which checks the structure GitHub actually requires,
and run it at three points: in CI on every PR, in the version job before the
tag is pushed, and in the release job before the major alias moves. A tag is
public and immutable the moment it exists, so validating after the push
would be too late — v1.0.0 will always name a broken action.

Tests cover 12 cases in both directions, including the exact dedented-heredoc
shape that shipped as v1.0.0 and the YAML-valid-but-unloadable forms a parse
check misses. They run in CI, since a guardrail that cannot fail is not one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VSrrciEDBTuFNMtKocScML
@SafeEval
SafeEval marked this pull request as ready for review July 30, 2026 20:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant