Skip to content

[finding] ci-failure.mjs's self-test prose is parsed as a workflow command — every PR's green lint check carries a real failure annotation #11886

Description

@os-steve

Found while measuring annotation visibility for #10803 (PR #11883). Not fixed there — out of scope for that card.

What happens

scripts/pm/ci-failure.mjs:2343 prints a line of self-test prose that names a workflow-command token literally:

' tail with no `##[error]` in it is labelled a window rather than an anchor. And a `fix`\n'+

The Actions runner does not know the token is being quoted. It parses it, and emits a genuine annotation. Measured on a real PR head sha (f9ed462900fbeb07383386bc9ca2bbfc3de5d294, run 32783785081, job Lint & Repo Gates), via GET /repos/{owner}/{repo}/check-runs/97611285494/annotations:

level=failure path=.github line=32 title=
msg=` in it is labelled a window rather than an anchor. And a `fix`

That is an annotation_level: failure on a check run whose conclusion is success.

Why it is worth fixing

  1. It is wrong on its face. A failure-level annotation on a green required check is a claim that something failed. Nothing did. The step (PM ci-failure self-test, Fix CI test failures: syntax error and undefined schema references #33) passes.
  2. It fires on every pull request. The step has no paths: filter, so this annotation is minted on every PR run in the repo, permanently.
  3. It degrades the annotation surface that other gates depend on. This was found precisely because [finding] check-release-section-coverage --strict has no standing caller — the advisory half is the only half that runs #10803 is about whether an advisory ::warning:: is visible enough on a PR. On the head measured, there were 5 annotations across 31 check runs — and 1 of the 5 is this artifact. A repo that teaches its readers that failure-level annotations on green checks are meaningless is training them to skim exactly the surface check-release-section-coverage reports into.
  4. It is self-referential: the script whose job is reading CI failure output is manufacturing a fake one.

Likely shape of the fix

Do not print the literal token. The usual options, cheapest first: break the token so the runner's parser does not match it while a human still reads it (e.g. an escaped or zero-joined spelling), or route the self-test's prose through a helper that neutralises ##[...] and ::...:: before printing. A regression pin belongs with it — this class is invisible to every local run, because the transformation only happens on a runner.

Worth checking whether any sibling script prints the same token in prose; this was found by reading one job's annotations, not by a scan.


Generated by Claude Code

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions