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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions