Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

169 Commits

Folders and files

Repository files navigation

OpenWrt PR Gatekeeper

A modern GitHub App webhook engine running on Cloudflare Workers that automates Pull Request validations, styling constraints, OpenWrt build recipe (Makefile) checks, downstream patches testing, and timeline triage.

Features Matrix

The validation engine splits checks into three distinct asynchronous status streams grouped natively under the GitHub App interface:

1. Formalities Check

Focuses on Git history hygiene, developer metadata constraints, and layout standards:

  • Branch Target Enforcement: Ensures pull requests originate from dedicated feature branches, blocking accidental direct PRs from main, master, or active stable branches.

Tip

Maintainers can bypass this check by posting a pull request comment containing [allow branch] (or [allow-branch]). Alternatively, if the PR author is a maintainer, they can also include [allow branch] in the PR description. See Who counts as a maintainer.

  • Stable Branch Backports Enforcement: Ensures that pull requests targeting active stable/backport branches (e.g., openwrt-25.12, openwrt-24.10) contain the context line (cherry picked from commit ...) in every commit message.

Tip

Maintainers can bypass this check by posting a pull request comment containing [allow cherry-pick] (or [allow-cherry-pick]). Alternatively, if the PR author is a maintainer, they can also include [allow cherry-pick] in the PR description. See Who counts as a maintainer.

  • New Package on a Release Branch (check_new_package_on_stable): On a pull request whose base is a release branch (openwrt-XX.YY, lede-17.01, for-XX.YY), resolves every package Makefile it adds to its package root and asks the default branch, in one GraphQL lookup, whether that path has ever existed in its history. The roots that never did are reported: release branches carry security and bug-fix backports only, and a new package lands on the default branch first. A package the default branch has ever carried, including one it later moved or dropped, is never reported, and neither is a Makefile below a package root. The lookup covers the first 12 added roots and says so when there are more. The default-branch pull request may still be open beside this one, which the lookup cannot see, so this is a warning by default — set "check_new_package_on_stable": "error" to enforce it.

  • Merge Commit Elimination: Rejects merge commits inside the PR tracking chain to preserve a clean linear history.

  • Identity Integrity: Validates author and committer name formats and strictly blocks generic GitHub noreply.github.com email addresses.

  • Linked GitHub Account: Verifies that the commit author email address is registered and verified on a GitHub account, linking the commit to a valid GitHub username. Can be downgraded to a non-blocking warning or disabled entirely, see require_linked_github_account below.

  • Autosquash Compliance: Automatically bypasses style constraints for development-phase fixup! and squash! syntax blocks.

  • Revert Compliance (allow_revert): Accepts the subject line git revert generates — Revert "<original subject>", its nested Revert "Revert "<original subject>"" form, and the prefixed <package>: Revert "..." variant. The quoted text is copied verbatim from the reverted commit, so the prefix and lowercase rules are not applied to it, and the Revert "..." wrapper is excluded from the subject length limits. A revert also puts PKG_VERSION and PKG_RELEASE back to the values that preceded the reverted commit, so those restored values are accepted instead of a bump (see PKG_RELEASE Validation below). These relaxed rules require the body to identify what is being reverted — either the This reverts commit <sha>. line git revert generates or the Reverts <owner>/<repo>#<number> reference GitHub adds — so that a subject merely shaped like a revert does not lift them.

  • Subject String Hygiene: Enforces <package name or prefix>: prefix headers (including versioned packages like lua5.4: , repository files like CONTRIBUTING.md: , README.md: , rules.mk: , feeds.conf.default: , dotfiles like .gitignore: , .github/workflows: , or source tree paths such as tools/cmake: , toolchain/musl: , package/network/services/hostapd: ), checks lowercase starting strings post-prefix, rejects trailing periods, and requires a blank line between the subject and the description body.

  • Subject Scope Check (check_subject_scope): Compares the subject prefix with the files the commit changes, and warns when it names none of them — hostapd: fix WPS on a commit that only touches package/network/services/dropbear/. Any part of a chained or path prefix may match any path segment of any changed file, or a package name the touched Makefiles define (PKG_NAME, define Package/<name>, kernel modules as kmod-<name>), so libiwinfo: on iwinfo and video-support: on hardware-support pass. So do the subsystem words of the submission guidelines (kernel, build, toolchain, tools, target, scripts, config, ci, docs, i18n) where they cover a changed file, treewide, the repository's own name, and anything in subject_scope_prefixes. Commits made only of quilt patches, only of shared build files, or (in openwrt/openwrt) only of files under target/ are not graded: their prefix legitimately names an upstream subsystem, a device or a subtarget. Reverts are graded on the quoted subject; verbatim backports and fixup!/squash! commits are skipped, and a Makefile that could not be read leaves the prefix unchecked. It catches typos and copy-paste slips, not every mislabel. Against 18,000 merged commits it flagged 0.54% of those it graded, about half of them deliberate prefixes naming the package a change is for, so this is a warning by default and should stay one.

  • Length Constraints: Implements dual-layered (soft and hard) line width boundaries for both subject lines and description body text blocks.

  • Signed-off-by Check: Ensures a consistent, properly structured Signed-off-by: declaration is present and matches the original author metadata.

  • Signature Verification: Validates cryptographic GPG/SSH commit signatures if present.

  • Imperative Mood Warning (warn_imperative_mood): Issues a non-blocking warning when the subject starts with a past-tense or gerund verb after the prefix — the guidelines ask for "add support for X", not "added support for X" (default true).

  • Description Quality Warnings: Inspects message bodies and issues non-blocking warnings for lazy/identical description text mirroring the subject or for completely missing reference links (changelogs/release notes).

  • Mandatory Description Body: Rejects commits whose description body is empty or contains only trailers (e.g. Signed-off-by:). Every commit must include a meaningful explanation of what the change does and why.

  • OpenWrt Spelling Verification (check_openwrt_spelling): Inspects both the subject line and description body to ensure the correct casing of "OpenWrt" is used, issuing warnings for incorrect capitalizations like "OpenWRT" or "Openwrt" (ignoring URLs, code blocks, and trailers).

Note

To keep API/subrequest usage predictable on extreme PRs, commit-message auditing is intentionally capped to the first 300 commits. If a PR exceeds this size, the check output includes an explicit warning about the reduced commit audit scope.

Important

A green check never means "not checked". When a pull request is too large for the available API budget — file lookups skipped, upstream comparisons skipped, or the commit scan capped — a check that found no problems reports GitHub's neutral conclusion titled Partially checked instead of a pass. neutral does not block the pull request; it only stops the bot from claiming it inspected something it never fetched. A check that found real problems still fails.

2. Makefile Check

Inspects file modification trees targeting OpenWrt build recipes:

  • PKG_VERSION Sync: Validates that if a version bump is introduced inside a Makefile, the matching version string exists within the commit subject line context.

  • Mandatory Metadata (check_openwrt_meta): Enforces the inclusion of PKG_MAINTAINER, PKG_LICENSE, and PKG_LICENSE_FILES variables whenever a new package is introduced (fully configurable list). New LuCI packages (built on luci.mk) are only asked for PKG_LICENSE — their maintainer field is generated by luci.mk and they carry SPDX headers instead of license files. A new package that includes u-boot.mk or trusted-firmware-a.mk is asked for neither PKG_LICENSE nor PKG_LICENSE_FILES, which those helpers set.

  • SPDX License Check (check_spdx_license): Validates every identifier in a PKG_LICENSE value against the official SPDX license list (all 701 current identifiers, 85 exceptions and 32 deprecated ids are embedded in cloudflare-worker/src/spdx-licenses.js, generated from spdx/license-list-data). Non-blocking warnings cover deprecated ids (GPL-2.0 → GPL-2.0-only/GPL-2.0-or-later), wrong capitalization (BSD-2-clause), informal spellings (GPLv2, GPL-3.0-later, MIT/X11), hyphenated exceptions (GPL-2.0-or-later-with-Autoconf-exception-2.0 → ... WITH Autoconf-exception-2.0), bare license families (BSD, Apache, Public-Domain) and unknown placeholders (VARIOUS, Custom, COPYING). A replacement is only ever suggested after it has been confirmed to be a real SPDX identifier; where SPDX retired an id without a successor the warning names none. LicenseRef-<name> — the SPDX form for a license that is not on the list — is accepted as-is (default true).

    [!NOTE] spdx-licenses.js is generated — never edit it by hand. Run node scripts/update-spdx-data.mjs to pull a newer SPDX release; the file records the release it came from in SPDX_LICENSE_LIST_VERSION, and the test suite verifies that every replacement it names is itself a valid identifier.

  • Init Script Check (check_init_scripts): Issues non-blocking warnings when a newly added file that looks like an init script (*.init or under etc/init.d/) does not start with #!/bin/sh /etc/rc.common or defines no START= priority. rc.common's enable() only creates the /etc/rc.d/S<START><name> boot symlink when START is set, so such a script is not started at boot through that path — the file is identified by its path alone, so the wording asks the author to confirm rather than asserting the service is broken (default true).

  • Conffiles Tracker: Mandates the definition of the Package/.../conffiles tracking macro whenever configuration is installed — an INSTALL_CONF line or a destination under $(1)/etc/config. Other payload under /etc (capability files, profile.d snippets, …) is not configuration and does not trigger the demand. Each entry must be one unindented absolute path per line, or a make expansion that produces one; a file must not end with a slash, while a directory may be written with or without it.

  • Line Ending Sanitization: Inspects modifications for Windows-style Carriage Returns (CRLF) to guarantee exclusive UNIX (LF) formatting compliance.

  • Trailing Newline Check: Verifies that newly created or modified files end with a trailing newline character, catching the common \ No newline at end of file issue in diffs (customizable level: warning/error/disabled).

  • PKG_RELEASE Validation: Enforces correct release values on package changes: new packages must initialize PKG_RELEASE to 1, version updates must reset PKG_RELEASE to 1, and modifications to package files must be accompanied by a version/release change (customizable level: warning/error/disabled). Packages modified exclusively by revert commits are exempt from the reset/initialize rules, because a revert restores the version and release of an already released state. They must still carry a release bump if the revert changes package content without touching the version or release, otherwise users would never receive it. Packages built on a shared helper that never uses PKG_RELEASE (u-boot.mk, trusted-firmware-a.mk, luci.mk — LuCI versions come from git history) are exempt from all release requirements, and so is any package with no PKG_RELEASE and no version anyone could move: package/kernel/linux is the kernel itself and package/kernel/bpf-headers derives its PKG_VERSION from the kernel's, so asking either for a bump is advice nobody can follow. A package that still defines a literal PKG_VERSION, PKG_SOURCE_VERSION or PKG_SOURCE_DATE has something to bump and is asked for it as before.

  • PKG_HASH Validation (check_pkg_hash): Points out source checksums that did not move together with the version: a PKG_VERSION/PKG_SOURCE_VERSION/PKG_SOURCE_DATE change that keeps the old PKG_HASH, and new packages that download a plain archive without defining one. Whether the sources really changed is an inference the bot cannot prove, so these are warnings by default — set "check_pkg_hash": "error" to enforce them. Packages using PKG_SOURCE_PROTO (git, svn, …) are left alone, because their generated tarball is not always reproducible and the docs name cases where PKG_MIRROR_HASH is deliberately omitted. Checksum values themselves are graded the way the build system treats them: a value that is neither a SHA256 nor skip makes scripts/download.pl abort and is always an error, while an MD5 (which check_hash only calls a deprecated hash) and skip (explicitly honored, but turns verification off) are warnings. Like the release audit, the bump comparison trusts the pull request's own diff — a version difference caused only by a stale base.sha is never reported.

  • Source Pin Validation (check_pkg_source_pin): Checks the shape of the two variables a repository checkout is pinned with, on the lines the pull request itself writes. PKG_SOURCE_DATE has to be a real date in YYYY-MM-DD form, and PKG_SOURCE_VERSION has to name a revision — not a branch head such as master, which moves under every build, and not an abbreviated hash in a git checkout. A new package that pins a revision with neither a date nor a PKG_VERSION is told its version would read 0~<revision> (see include/download.mk). Computed values (v$(PKG_VERSION)), tags and SVN revision numbers are left alone, and the date is never required to move with the revision: upstream can commit twice in one day. The rule comes from the package policies, not from anything the build refuses, so these are warnings by default — set "check_pkg_source_pin": "error" to enforce them. PKG_RELEASE and the checksums stay with the two audits above.

  • DTS Checklist (check_dts_checklist): Checks device trees a pull request adds (.dts/.dtsi/.dtso) for the three items of OpenWrt's DTS checklist a diff settles on its own: a missing SPDX-License-Identifier file header, a redundant default-state = "off" on an LED inside a compatible = "gpio-leds" node (off is the default already — see commit 0d23fd2), and the deprecated label = "<colour>:<function>" LED naming that openwrt/openwrt's own review rules replace with color/function. An added file carries its whole text in the diff, so no file is read unless a later commit changed it again. A device tree the pull request only modifies is not checked, and the audit names it rather than passing it. Other LED classes (register-bit-led and friends), &leds overrides whose gpio-leds node lives in an included .dtsi, reverts and verbatim backport commits are left alone. The wiki's device support policies still document the label = "color:function" scheme; where the two disagree this check follows the rules file in the repository. The wiki asks rather than requires, so these are warnings by default — set "check_dts_checklist": "error" to enforce them.

  • UCI Config Validation: Ensures that any configuration files destined to be installed into /etc/config/ conform to the standard OpenWrt UCI format (consisting of only package, config, option, list statements, comments, and empty lines). A Makefile install line naming the exact file is authoritative about its destination, so a file sharing a base name with the package's config (uhttpd.acl next to uhttpd.config) is never mistaken for UCI just because of the shared stem.

  • PKG_NAME Reuse Prevention: Ensures PKG_NAME is not reused inside call, define, and eval Makefile lines, requiring the literal package name instead to keep recipes readable and searchable (default true).

  • Dead Package Variable Detection: Rejects per-package variables (PROVIDES, TITLE, DEPENDS, MAINTAINER, ...) assigned at the top level of a package Makefile, where the build system resets them before the package definition is read — so the assignment silently does nothing. Suggests the working replacement: the define Package/<name> block, the PKG_* twin (e.g. PKG_MAINTAINER), or the LuCI equivalent (PKG_PROVIDES, LUCI_TITLE, ...) for Makefiles built on luci.mk. Always on — a dead assignment is a bug in any repository, so there is nothing to configure.

  • Feed Buildbot-Default Guard (check_buildbot_default): Flags DEFAULT assignments (=, :=, ::=, +=, ?=, including backslash-continued ones) that condition inclusion on BUILDBOT inside feed repositories — both brand-new assignments and a BUILDBOT condition newly added to an existing one, since this forces a package into buildbot default images without going through the main openwrt/openwrt repo's own default-package process (customizable level: warning/error/disabled; skipped entirely for the main repo).

3. Patches Check

Scans the contribution tree for nested downstream patch targets:

  • Git-Am Compliance: Checks that each changed .patch file carries the From <hash> Mon Sep 17 00:00:00 2001 envelope line, From: and Subject:, as git format-patch writes them. The envelope is required because patches are also concatenated into one mbox stream for git am, where it is the only thing separating them; Date: is optional, git am fills it in. The report names only the headers that are missing. A patch the pull request adds, or an edit that removes a header, fails the check (customizable level: warning/error/disabled); editing an older patch that already lacks them only warns.
  • Refreshed Patches (check_patch_refresh): Reports a patch under patches*/, backport-*/, pending-*/ or hack-*/ whose diff was not produced by make package/<pkg>/refresh: a diff --git or index line from git format-patch, an Index: line, ---/+++ timestamps, or paths without the a/ and b/ prefixes. Only what the pull request itself introduces counts: a marker already in the file, or carried over with it by a rename, is left alone. The two rules read different parts of the file: the headers above the diff come from git format-patch, the diff below them from the refresh target, which keeps those headers, so a patch made with git format-patch and then refreshed meets both. The rule is openwrt/packages' CONTRIBUTING.md, and openwrt/openwrt has merged git format-patch output, so these are warnings by default, reported next to the header warnings, and filtered against the upstream commit of a backport the same way.

Automated Triage & Stale PR Management

  • not following guidelines: A high-visibility tag automatically attached to the PR if any critical validation check drops a failure blueprint. Clears itself upon a successful push.
  • add package / drop package: Dynamically analyzes unified diff targets to label tracking trees introducing or purging software packages. Both are removed again once the pull request no longer adds or drops a package.
  • Stable Branch Tracking: Auto-generates matching grey release tags (e.g., release/24.10, release/25.12) whenever a PR targets an active release backport branch, and removes a release tag that no longer matches after the pull request is retargeted. Labels matched from labeler.yml are only ever added.
  • Issue Labeller: Replaces the GitHub Actions issue-labeller.yml workflow. When a bug-report issue is opened with the trigger label, the bot validates form fields and applies labels based on a declarative .github/issue-labeller.yml configuration file (same spirit as labeler.yml for PRs — label name → list of conditions). Supports template variables ({major}, {segment0}, etc.), format validation (regex), existence checks (tag/path via GraphQL), substring matching, and presence checks. Falls back to sensible defaults if no config file exists. Disabled by default — enable per-repository with "enable_issue_labeller": true.
  • Stale PR Cleanup: A daily scheduled cron task (05:30 UTC) scans all repositories where the App is installed. If explicitly enabled in a repository's configuration (`"enable_stale_bot": true`), it marks PRs containing the `not following guidelines` label as `stale` (with a warning comment) after 14 days of inactivity, and closes them after another 14 days of silence. Only contributor activity resets the countdown: pushed commits, force-pushes, reopens, and comments or reviews from people. Comments from GitHub Apps, *[bot] accounts and the machine accounts listed in stale_ignored_users are ignored, so an automated review can never keep a dead PR alive forever. Pushing new commits also removes the stale label immediately via the webhook, without waiting for the nightly scan. The scan asks GitHub for each repository's configuration and its labelled pull requests in two GraphQL queries, timelines included, so its request count no longer grows with the number of stale pull requests. A pull request whose timeline is longer than the fetched window (the most recent 100 entries) is left untouched rather than judged on incomplete history.

Tip

Stale PR cleanup is completely disabled by default. If a repository wants to enable this automated cleanup flow, it must commit a .github/formalities.json file in its default branch containing "enable_stale_bot": true.

  • Clean Timelines: Drops descriptive, cleanly formatted markdown dashboards into the PR conversation section on failure, automatically editing or removing itself once instructions are followed to keep the timeline clean.
  • Header Footnote: PR comments include a dynamic footnote linking directly to this repository's issues page for reporting validation bugs.

Who counts as a maintainer

Override commands ([allow branch], [allow cherry-pick]) and comment-triggered re-checks are only honored for maintainers. A user qualifies if either of the following holds:

  • their author_association on the pull request or comment is OWNER, MEMBER or COLLABORATOR, or
  • they hold write, maintain or admin access on the repository.

The second rule exists because GitHub reports author_association: CONTRIBUTOR (or NONE) for maintainers who keep their organization membership private, which would otherwise lock them out of their own overrides. Resolving it costs one extra API lookup per login — and a lookup can retry on transient failures, so it may spend more than one outgoing subrequest — so it is only asked when the association alone is not enough. Where the answer cannot change the outcome it is not asked at all: a PR description without an override command, and a comment in the thread that carries none, are decided by the association alone. A comment that arrives as its own event is the exception — the commenter is resolved before the body is read, because only a maintainer's comment re-runs the checks.

Answers are cached for the duration of the webhook and bot accounts are never resolved. An override is honored only for a maintainer the bot could confirm, so a lookup GitHub refused outright (403) is "not a maintainer" — but a lookup that failed (a 5xx, a network error) is neither: the override is not honored and the check it would have waived reports itself unchecked rather than violated, because a pull request must not fail on an answer nobody got.

Note

Read-only and triage collaborators cannot use the overrides. Organization members are trusted through their association even without write access, matching the behavior the bot has always had.


Setup & Deployment

The engine is built as a headless JavaScript service hosted on Cloudflare Workers. The runtime has no third-party production dependencies — the Worker ships only this repository's own source — which keeps the deployed attack surface small. Tests run on Node's built-in test runner, so development needs no dependencies either.

1. GitHub App Configuration

The GitHub App requires the following permissions and event subscriptions:

  • Repository permissions:
    • Checks: Read & write (to publish validation audit check runs)
    • Pull requests: Read & write (to post the audit comment and manage triage labels)
    • Issues: Read & write (to label and comment on issues, and required by GitHub for the issues and issue_comment event subscriptions below)
    • Contents: Read-only (to fetch repository-specific configurations like .github/formalities.json)
    • Metadata: Read-only (mandatory for every App)
  • Event Subscriptions:
    • Subscribe to Pull request events (triggers on opened, synchronized, reopened, and edited when the title, body or base branch changed).
    • Subscribe to Issue comment events (triggers on created — required for the override commands and comment-triggered re-checks).
    • Subscribe to Issues events (triggers on opened, reopened, and edited when the body changed — required for the issue labeller feature).

Note

The maintainer fallback described in Who counts as a maintainer calls GET /repos/{owner}/{repo}/collaborators/{username}/permission. Whether an installation is allowed to call it is decided by GitHub, so verify it against a real installation rather than assuming the permissions listed above are enough. If the call is refused (403 Resource not accessible by integration), the lookup fails closed: maintainers with a private organization membership are treated as contributors, and the refusal is visible in the Worker logs.

2. Cloudflare Worker Configuration

  1. Deploy the Worker to your Cloudflare account (managed automatically via the CI/CD pipeline).
  2. In the Cloudflare Dashboard under Workers & Pages -> Settings -> Variables, configure the following variables as Secrets (encrypted):
    • APP_ID: Your GitHub App ID (e.g., 123456).
    • WEBHOOK_SECRET: The secret token used to verify GitHub webhook HMAC-SHA256 signatures.
    • PRIVATE_KEY: The complete text of your GitHub App private key PEM file.

Important

The private key must be in PKCS#8 format (starting with -----BEGIN PRIVATE KEY-----). If your downloaded key starts with -----BEGIN RSA PRIVATE KEY----- (PKCS#1), convert it using: openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in rsa_key.pem -out pkcs8_key.pem

3. CI/CD Pipeline (GitHub Actions)

The repository includes a GitHub Actions workflow in .github/workflows/deploy.yml that builds and deploys the Worker automatically on every push to main:

  • Runs on a lightweight ubuntu-slim container.
  • Deploys only after the shared test and dry-run build workflow passes, and runs one deployment at a time.
  • Automatically injects the build commit hash (DEPLOY_HASH) and deploy timestamp in Prague timezone (DEPLOY_DATE) into the wrangler variables before deployment.
  • To enable deployment, add your CLOUDFLARE_API_TOKEN (with edit permissions for Workers) as a secret in your GitHub repository's Settings -> Secrets and variables -> Actions.

4. Repository Level Customization

If individual source repositories wish to tweak defaults or scale back rule restrictions, creators can commit a custom .github/formalities.json file inside their repository branch root.

Some configuration keys offer advanced options:

  • check_openwrt_meta: Can be true (enforces standard PKG_MAINTAINER, PKG_LICENSE, and PKG_LICENSE_FILES for new packages), false (disabled), or an array of custom required fields (e.g., ["PKG_MAINTAINER", "PKG_LICENSE"]).
  • check_patch_headers: Can be true (default, hard error), "warning" (non-blocking), or false to disable. Even at true, only a patch the pull request adds, or an edit that removes a header, fails; an edit to an older patch that already lacks the headers is reported as a warning.
  • check_patch_refresh: Can be "warning" (default, non-blocking), true/"error" (hard error), or false/"disabled" to disable. Graded on its own, whatever check_patch_headers is set to.
  • check_trailing_newline: Can be true (default, hard error), "warning" (non-blocking), or false to disable.
  • check_binary_files: Can be true (default, hard error), "warning" (non-blocking), or false/"disabled" to disable. Rejects a file git carries as binary, in any encoding, since such a file reaches none of the content checks and cannot be reviewed in a diff. Deleting one is always allowed.
  • check_pkg_release: Can be "warning", "error", or false to disable.
  • check_pkg_hash: "warning" (default) reports checksum findings without blocking, "error" enforces them, false disables the check. While it is enabled, a checksum value the build cannot use is an error at either level.
  • check_new_package_on_stable: Can be "warning" (default, non-blocking), "error"/true (hard error), or false/"disabled" to disable. Only applies to pull requests whose base branch is a release branch; a backport of a package the default branch already carries is never reported.
  • check_subject_scope: Can be "warning" (default, non-blocking), "error"/true (hard error, not recommended — a prefix naming the package a change is for rather than the file it edits is legitimate and looks exactly like a mislabel), or false/"disabled" to disable.
  • subject_scope_prefixes: Prefixes that name no directory and are always accepted (default: ["lint", "css", "housekeeping", "meta", "wiki", "npm", "vscode", "editor", "devcontainer", "i18n", "docs", "treewide", "tree-wide"]). Add a repository's own habitual prefixes here rather than switching the check off.
  • check_pkg_source_pin: "warning" (default) reports PKG_SOURCE_DATE/PKG_SOURCE_VERSION shape findings without blocking, "error" enforces them, false disables the check.
  • check_dts_checklist: "warning" (default) reports DTS checklist findings without blocking, "error" enforces them, false disables the check. Only device trees the pull request adds are checked.
  • require_linked_github_account: Can be true (default, hard error), "warning" (non-blocking), or false/"disabled" to disable.
  • check_uci_config: Set to true (default) to validate UCI configurations. Set to false or "disabled" to disable.
  • check_spdx_license: Set to true (default) to validate PKG_LICENSE identifiers against the official SPDX license list, or false to disable.
  • check_init_scripts: Set to true (default) to warn when a new init script lacks the #!/bin/sh /etc/rc.common interpreter line or a START= priority, or false to disable.
  • check_space_after_assignment: Set to true (default) to detect and reject spaces/indentation immediately after the := assignment operator in Makefiles, or false to disable.
  • check_missing_colon: Set to true (default) to detect and reject the use of = instead of := for standard variables (e.g. PKG_NAME, TITLE, URL, etc.) in Makefiles, or false to disable.
  • check_makefile_indentation: Set to true (default) to validate package metadata/description blocks are indented with 2 spaces and install/build blocks are indented with tabs in Makefiles, or false to disable.
  • check_pkg_name_reuse: Set to true (default) to detect and reject reuse of the PKG_NAME variable in call, define, and eval lines, or false to disable.
  • check_buildbot_default: Can be "warning" (default, non-blocking), "error"/true (hard error), or "disabled"/false to disable. Never applies to the main openwrt/openwrt repository, where default package selection legitimately lives.
  • show_force_push_tip: Set to true (default) to append a helpful tip regarding how to correct validation errors using force-pushing. Set to false to disable.
  • check_openwrt_spelling: Set to true (default) to validate the correct capitalization of "OpenWrt" in commit subjects and descriptions. Set to false to disable.
  • allow_revert: Set to true (default) to accept the subject format produced by git revert (Revert "<original subject>", nested reverts, and the prefixed <package>: Revert "..." variant) and the PKG_VERSION/PKG_RELEASE values a revert restores, for commits whose body references the reverted commit (This reverts commit <sha>. or Reverts <owner>/<repo>#<number>). Set to false to hold revert commits to the regular subject and release bump rules.
  • enable_stale_bot: Set to true to enable the stale PR bot cleanup for this repository. Defaults to false (opt-in).
  • stale_ignored_users: List of account logins whose comments and reviews never reset the stale countdown (default: ["openwrt-ai"]). GitHub Apps and *[bot] accounts are always ignored automatically; this list exists for automation that runs on a plain user account.
  • enable_labeler_yml: Set to true to enable dynamic pull request labeling based on matching files in the .github/labeler.yml configuration file. Defaults to false (opt-in).
  • enable_issue_labeller: Set to true to enable automated issue form validation and labelling (replaces the GitHub Actions issue-labeller.yml workflow). Defaults to false (opt-in).

Here is a comprehensive example containing all available toggle options:

{
  "check_branch": true,
  "check_merge_commits": true,
  "check_noreply_email": true,
  "check_signoff": true,
  "check_signature": true,
  "allow_autosquash": true,
  "allow_revert": true,
  "enable_comments": true,
  "show_force_push_tip": true,
  "max_subject_len_soft": 60,
  "max_subject_len_hard": 80,
  "max_body_line_len": 100,
  "warn_duplicate_body": true,
  "warn_generic_subjects": true,
  "warn_imperative_mood": true,
  "require_release_notes": true,
  "require_body": true,
  "check_pkg_version": true,
  "check_binary_files": true,
  "check_crlf": true,
  "check_trailing_newline": true,
  "add_package_label": true,
  "drop_package_label": true,
  "branch_labeling": true,
  "check_openwrt_meta": true,
  "check_spdx_license": true,
  "check_init_scripts": true,
  "check_conffiles": true,
  "check_uci_config": true,
  "check_dts_checklist": "warning",
  "check_space_after_assignment": true,
  "check_missing_colon": true,
  "check_makefile_indentation": true,
  "check_pkg_name_reuse": true,
  "check_buildbot_default": "warning",
  "check_patch_headers": true,
  "check_patch_refresh": "warning",
  "check_pkg_release": "warning",
  "check_pkg_hash": "warning",
  "check_pkg_source_pin": "warning",
  "check_new_package_on_stable": "warning",
  "require_linked_github_account": true,
  "check_openwrt_spelling": true,
  "check_subject_scope": "warning",
  "subject_scope_prefixes": ["lint", "css", "housekeeping", "meta", "wiki", "npm", "vscode", "editor", "devcontainer", "i18n", "docs", "treewide", "tree-wide"],
  "enable_stale_bot": false,
  "stale_ignored_users": ["openwrt-ai"],
  "enable_labeler_yml": false,
  "enable_issue_labeller": false
}

5. Issue Labeller Configuration (.github/issue-labeller.yml)

When enable_issue_labeller is true, the bot fetches .github/issue-labeller.yml from the repository's default branch. The format mirrors labeler.yml — each top-level key is a label name (supporting {template} variables), followed by a list of conditions that must all match for the label to apply.

Template variables (extracted from the matched field value):

  • {value} — full trimmed value
  • {segment0}, {segment1}, … — slash-separated parts (ramips/mt7621 → ramips, mt7621)
  • {major}, {minor}, {patch} — dot-separated parts (24.10.0 → 24, 10, 0)
  • {hash} — trailing hex string after last - (r28945-24a9f1c224 → 24a9f1c224)

Condition types:

  • field — form field name to check (case-insensitive, normalized to snake_case)

  • format — regex the field value must match (invalid format → invalid label + comment)

  • exists — existence check via GraphQL: "tag:v{value}" or "path:target/linux/{segment0}" (anything that is not tag: is read as a path)

  • contains — case-insensitive substring match

  • not_empty — field must be non-empty

  • hint — what the reporter should do about a failed check, shown in the invalid-form comment in place of the built-in hint. Write it as an instruction, not a description:

    hint: Run `. /etc/openwrt_release && echo $DISTRIB_RELEASE` on the device and paste that value alone.

Meta keys (underscore-prefixed, not labels):

  • _trigger_label — label required on the issue to start processing (default: to-triage)
  • _invalid_label — label added when validation fails (default: invalid)
  • _remove_labels — labels always removed after processing (default: ["to-triage"])
  • _invalid_comment_header / _invalid_comment_footer — text above and below the list of invalid fields. The per-field line between them is fixed, and names the field, its value and the hint
  • _valid_comment — posted when an edit fixes a previously invalid form
  • _require_form — also process issues that never received _trigger_label, and flag ones whose body has no ### sections at all as having bypassed the issue template. OWNER, MEMBER and COLLABORATOR are exempt, so maintainers' free-form tracking issues are left alone (default: false)
  • _no_form_comment — posted to those issues

Note

Every default message the bot posts lives in one place — ISSUE_LABELLER_MESSAGES in cloudflare-worker/src/config.js — with the config key that replaces it named beside each entry. The shipped text is English and its hints name OpenWrt's own commands, which suits openwrt/openwrt and nothing else in particular; a feed or a fork rewords or translates all of it from issue-labeller.yml, without touching code.

Re-checking on edit. The labeller runs on opened, edited and reopened. The trigger label is consumed on the first run, so a re-run re-enters on the _invalid_label instead: an issue stays supervised until its form validates. Consequences:

  • The bot keeps one comment per issue, tagged with an invisible <!-- issue-labeller --> marker and edited in place. Three edits produce one comment, not four.
  • When the reporter fixes the form, _invalid_label is removed and the comment is replaced by _valid_comment, so nobody is left staring at a stale complaint.
  • Labels that validated are kept when another field fails — a report with a good target and a mistyped release still gets its target/* label.

Per-label metadata (inside a label block, underscore-prefixed):

  • _color — hex color used when the label is auto-created (default: ededed)
  • _description — description used when the label is auto-created

Example:

# .github/issue-labeller.yml
_trigger_label: "to-triage"
_invalid_label: "invalid"
_remove_labels: ["to-triage"]

"release/{major}.{minor}":
  _color: "6b7280"
  _description: "OpenWrt release version reported"
  - field: "release"
    format: '^\d+\.\d+\.\d+(-rc\d+)*$|^\d+\.\d+-SNAPSHOT$'
    exists: "tag:v{value}"

"target/{segment0}":
  _color: "0e7490"
  _description: "Hardware target reported"
  - field: "target"
    format: '^[a-zA-Z0-9]+/[a-zA-Z0-9]+$'
    exists: "path:target/linux/{segment0}/{segment1}"
    hint: "Run `. /etc/openwrt_release && echo $DISTRIB_TARGET` on the device and paste that line — it is `target/subtarget`, e.g. `ramips/mt7621`, not the image file name"

"Official Image":
  - field: "image_kind"
    contains: "official"

"Self Built Image":
  - field: "image_kind"
    contains: "self"

"Supported Device":
  - field: "device"
    not_empty: true

If the file is absent, the bot falls back to the default rules shown above.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages