Skip to content

ci(build): replace Danger with a dependency-free PR title check - #93

Merged
bluestreak01 merged 3 commits into
mainfrom
ci/pr-title-check-without-bot-account
Sep 4, 2026
Merged

ci(build): replace Danger with a dependency-free PR title check#93
bluestreak01 merged 3 commits into
mainfrom
ci/pr-title-check-without-bot-account

Conversation

@bluestreak01

@bluestreak01 bluestreak01 commented Sep 4, 2026

Copy link
Copy Markdown
Member

Third and last of the PR-title-check ports, after questdb/questdb#7599 and questdb/questdb-enterprise#1202. Unlike the enterprise one this is a genuine replacement: Danger really does run here.

Danger read the title and reported through the questdb-butler account using a PAT in the DANGER_GITHUB_TOKEN secret (last updated 2026-01-28). Nothing about the job needed a separate identity, and because Danger is not a required status in this repo, that token expiring would have failed the check open rather than loudly. check.js now does the same work on the workflow's own GITHUB_TOKEN, with no yarn global add danger and no dependency at run time.

DANGER_GITHUB_TOKEN can be deleted from repository secrets once this settles.

The DangerPR title rename is safe here — checked, not assumed

Branch protection on main requires the 4 questdb.java-questdb-client Azure contexts — not Danger
Open PRs carrying a Danger status 6, all green — no red status is left stranded
Leftover Danger comments to adopt 0

So no ruleset or branch-protection edit is needed, and no open PR ends up waiting on a check that stopped reporting. questdb/questdb has since moved to the same PR title string in questdb/questdb#7599, but had to pay for it: its master ruleset names the context, so the rename had to be paired with a hand edit of the ruleset, and the gap between the two is a gap in which nothing can merge. Here the string was free.

The rules are essentially unchanged

The subType list already matches what this repo merges. Across 69 merged + 8 open PRs, only qwp (33), build (15), ilp (11), core (5) and conf (1) appear — every one already allowed. The current validator rejects 2/69 merged, and both are Bump version to 1.3.x-SNAPSHOT PRs opened by app/github-actions.

One rule is added: the Conventional Commits breaking-change marker, so feat(qwp)!: passes. Strictly more permissive — it cannot redden anything that passes today.

validate.test.js was vacuous

testInvalid passed a bare undefined onError, so assert.throws was satisfied by a ReferenceError rather than by the title being rejected — every negative case passed against a validator that accepts everything. Demonstrated rather than asserted:

replacing the regex check with an unconditional accept result
old validate.test.js passes
new validate.test.js fails

Carried over from the enterprise port

  • Every stale comment is cleared, not just the first. The OSS findComment returns one match, so a PR carrying two sheds one per run and keeps the rest.
  • Tests run in their own job. As a first step of the job that posts the verdict, a failing test stops check.js entirely, leaving a corrected title still carrying its rejection comment. On the enterprise PR this was not hypothetical — a checkout failure took out the test job while validate still posted its verdict.

Known wart, unchanged by this PR

The automated Bump version to 1.3.x-SNAPSHOT release PRs do not match the format and go red. Danger rejects them today too and they merge anyway, since the check is not required — so this is not a regression. If the check is ever made required, retitle them build: bump version to 1.3.x-SNAPSHOT; bare build: is already accepted.

Verification

  • node validate.test.js and node check.test.js pass; check.test.js covers 10 reporting scenarios.
  • Both suites are load-bearing: reverting the multi-comment fix, or neutering the validator, fails them.
  • This PR's own title is validated by the workflow it adds.

Danger JS read the pull request title and reported on it through the
questdb-butler account, using a personal access token held in the
DANGER_GITHUB_TOKEN secret. Nothing about the job needed a separate identity, and
the token's expiry would have quietly stopped the check: Danger is not a required
status here, so it would have failed open rather than loudly.

ci/validate-pr-title/check.js now does the same two things on the workflow's own
GITHUB_TOKEN: it posts the commit status and leaves a comment explaining a
rejection, updating that comment in place while the title stays wrong and deleting
it once the title is fixed. The yarn global add danger step and dangerfile.js are
gone, and the scripts use only the Node standard library. DANGER_GITHUB_TOKEN can
be deleted from the repository secrets once this has settled.

The status context is "PR title", not the "Danger" that Danger posted. The rename
is safe here and was checked rather than assumed: branch protection on main
requires only the four questdb.java-questdb-client Azure contexts, and every open
pull request that carries a Danger status carries a green one, so nothing is left
waiting on a check that stopped reporting. The copy in questdb/questdb cannot do
this, because its master ruleset names "Danger" exactly.

The subType list is unchanged. It already matches what this repository merges:
across 69 merged and 8 open pull requests only qwp, build, ilp, core and conf
appear, all of them already allowed. The rules gain one thing, the Conventional
Commits breaking-change marker, so feat(qwp)!: passes. That is strictly more
permissive and cannot redden a title that passes today.

validate.test.js loses a long-standing hole: testInvalid passed a bare undefined
onError, so the assertion was satisfied by a ReferenceError rather than by the
title being rejected, and every negative case passed against a validator that
accepts everything. Replacing the regex body with an unconditional accept now
fails the suite; before this change it passed.

check.test.js covers the reporting against a stubbed API, including the comment
lifecycle, the merge-group path, and the fail-closed behaviour that publishes a
verdict even when the run comes apart. Two details are load-bearing and pinned by
tests: the status must be posted on pull_request.head.sha, since GITHUB_SHA on a
pull_request event is the throwaway merge commit and a status there is invisible
to the pull request; and synchronize has to stay in the trigger list, because the
status is attached to a commit and a new head needs its own verdict.

The tests run in their own job rather than as a first step of the job that posts
the verdict, so a failing test cannot stop check.js from running and leave a
corrected title still carrying its rejection comment.
readme.md and the comment above CONTEXT both said questdb/questdb could not
rename its status context away from "Danger". It has: questdb/questdb#7599 moves
it to "PR title", the same string this repository posts, paired with a hand edit
of the master ruleset.

What is actually different survives the rename and is the part worth keeping,
because it is about what a rename costs rather than what the context is called.
Here the string is free: branch protection on main requires only the four
questdb.java-questdb-client Azure contexts, so nothing ever waited on "Danger"
and no open pull request was left behind a check that stopped reporting. There
the ruleset names the context, so the code and the ruleset are a contract, and
the gap between changing one and the other is a gap in which nothing merges.
The workflow name and the status context are both "PR title", so a pull request
showed three rows that read the same way: the "PR title" status, plus the
"PR title / test" and "PR title / validate" check runs GitHub names after the
workflow and its jobs.

The rules' tests move to pr_title_rules.yml behind a path filter, so they run when
ci/validate-pr-title or either workflow changes and stay off every other pull
request. The reason they are not a step of the job that posts the status is
unchanged and now structural rather than conventional: run there, a failing or
flaky test stops check.js from running at all, no status is posted, and the pull
request sits behind a check that is merely missing. Separate workflows cannot do
that to each other.

A pull request now shows the status and one "PR title / validate" row, and a
third only when the checker itself is under change, which is when the tests are
worth looking at.
@bluestreak01
bluestreak01 merged commit 981bdb0 into main Sep 4, 2026
10 of 12 checks passed
@bluestreak01
bluestreak01 deleted the ci/pr-title-check-without-bot-account branch September 4, 2026 15:37
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