From 925a383bc1e68cc3fdea2f3289d11afadaae7924 Mon Sep 17 00:00:00 2001 From: Saad Date: Sun, 1 Nov 2026 22:24:40 +0100 Subject: [PATCH] ci: skip the PR description check for Dependabot Dependabot writes its own body with none of the required headings, so every Dependabot pull request showed a red check that nothing could turn green. The author's login decides, so a person editing that body doesn't bring the check back. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/pr-description-check.yml | 2 + AGENTS.md | 2 + scripts/pr-description-check.test.ts | 47 ++++++++++++++++++++++ specs/hal-engine-doc-gates/spec.md | 7 ++++ 4 files changed, 58 insertions(+) create mode 100644 scripts/pr-description-check.test.ts diff --git a/.github/workflows/pr-description-check.yml b/.github/workflows/pr-description-check.yml index 36c4090d..10463c71 100644 --- a/.github/workflows/pr-description-check.yml +++ b/.github/workflows/pr-description-check.yml @@ -10,6 +10,8 @@ permissions: jobs: check-pr-description: + # Dependabot writes its own body with none of these headings, so its PRs could never pass; the author decides, not the actor. + if: github.event.pull_request.user.login != 'dependabot[bot]' runs-on: ubuntu-latest steps: - name: Check required sections diff --git a/AGENTS.md b/AGENTS.md index 66aed0dd..c003d8ea 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -271,6 +271,8 @@ test(tools): add registry conflict detection ### PR Description Template +CI's `check-pr-description` job requires `## Why`, `## What Changed` and `## Testing`. It skips a pull request Dependabot opened, since Dependabot writes its own body. + ```markdown ## Why diff --git a/scripts/pr-description-check.test.ts b/scripts/pr-description-check.test.ts new file mode 100644 index 00000000..eb4ce4b6 --- /dev/null +++ b/scripts/pr-description-check.test.ts @@ -0,0 +1,47 @@ +import {spawnSync} from 'node:child_process'; +import {readFileSync} from 'node:fs'; +import {join} from 'node:path'; +import {parseDocument} from 'yaml'; +import {root} from './lib/repo-root.mjs'; + +type Job = {if?: string; steps: {run: string; if?: string}[]}; + +const job = (): Job => { + const workflow = parseDocument(readFileSync(join(root, '.github/workflows/pr-description-check.yml'), 'utf8')).toJS(); + + return (workflow as {jobs: Record}).jobs['check-pr-description']; +}; + +const checkBody = (body: string) => { + const [step] = job().steps; + const {status, stdout} = spawnSync('bash', ['-c', step.run], { + encoding: 'utf8', + env: {...process.env, PR_BODY: body}, + }); + + return {status, stdout}; +}; + +describe('the pull request description check', () => { + it('skips a pull request Dependabot opened, by its author rather than whoever edits it', () => { + expect({condition: job().if, stepConditions: job().steps.map(step => 'if' in step)}).toEqual({ + condition: "github.event.pull_request.user.login != 'dependabot[bot]'", + stepConditions: [false], + }); + }); + + it('still passes a body with all three headings and fails a Dependabot-style one, naming each it lacks', () => { + expect({ + complete: checkBody('## Why\n\nA.\n\n## What Changed\n\nB.\n\n## Testing\n\nC.\n'), + dependabotStyle: checkBody('Bumps [@types/node](https://github.com/DefinitelyTyped) from 26.6.5 to 26.6.6.\n'), + }).toEqual({ + complete: {status: 0, stdout: 'All required PR description sections are present.\n'}, + dependabotStyle: { + status: 1, + stdout: + '::error::PR description is missing required sections: ## Why ## What Changed ## Testing\n' + + 'Required sections: ## Why, ## What Changed, ## Testing\n', + }, + }); + }); +}); diff --git a/specs/hal-engine-doc-gates/spec.md b/specs/hal-engine-doc-gates/spec.md index e6f93f68..690112de 100644 --- a/specs/hal-engine-doc-gates/spec.md +++ b/specs/hal-engine-doc-gates/spec.md @@ -61,6 +61,13 @@ Spike documents are covered differently. A spike records what was believed when Two steps of that job are deliberately outside the script: the spec anchor check and the changelog check both diff against `origin/main`, and neither behaves the same on a checkout as it does against a pull request's base. +## The pull request description check leaves Dependabot alone + +`pr-description-check.yml` asks for three headings that Dependabot's body never has, so every Dependabot pull request showed a red check nothing could turn green. + +- The check skips a pull request Dependabot opened, decided by its author rather than by whoever edits it ([validated by: skips a pull request Dependabot opened, by its author rather than whoever edits it](../../scripts/pr-description-check.test.ts#L26)). +- It still fails any other body that lacks a heading, naming each one ([validated by: still passes a body with all three headings and fails a Dependabot-style one, naming each it lacks](../../scripts/pr-description-check.test.ts#L33)). + ## Paths in prose resolve `npm run docs:check-paths` resolves every backticked repository path written in a document - `src/`, `scripts/`, `example/`, `smoke/`, `docs/`, `specs/`, `adrs/`, `dist/`, `.github/` and `.specify/`. A path that legitimately does not resolve is named in the script's own allowlist with the reason, because an exception has to be written down to be one; narrowing the pattern instead would hide every other path behind the same prefix. A path in prose is a citation, and a citation nobody resolves rots in silence: the file moves, the sentence naming it stays, and the next reader follows it to nothing.