How to work on gh-actions in a clone. The contributing guide covers how a change gets in.
actions/<name>/: one action per folder, named as inuses:, with its README. A Node action also holdssrc/, the committeddist/, and its ownpackage.json,package-lock.json,mise.toml,tsconfig.jsonandbiome.json. A composite action holds onlyaction.ymland its README..github/workflows/: the repository's own CI, andpreview-comment.yml, the reusable workflow that callers run.- The root: the tooling the actions share (Biome, TypeScript, run-run and lefthook), installed with pnpm, and the mise tasks that run in every action.
mise pins Node 24 and pnpm 10.30.3 in mise.toml, which needs mise 2026.6.6 or later. The repository has no mise.lock. mise.toml makes the root a mise monorepo with each folder in actions/ as a config root, so a task run at the root runs in every action that declares it. It also puts node_modules/.bin on the PATH, so the tools run by name.
mise run setup # mise install, pnpm install, then npm install in each Node action
pnpm lefthook install # the git hooks, once per clonepnpm doesn't run lefthook's install script (ignoredBuiltDependencies in package.json), so the hooks wait for lefthook install.
mise run check # Biome, then the type check, in every Node action
mise run tsc # only the type check
mise run test # the unit tests, in every action that has them
mise run build # build dist/ in every Node action, skipped when nothing changed
mise run check-dist # rebuild every dist/, and fail if it differs from the commitIn an action's folder, mise run <task> runs that action's task only.
- On each pull request: Check actions dist sync diffs the comment code that
preview-releaseandmonorepo-preview-releaseshare, then runsmise run testandmise run check-dist. Workflow audit runs zizmor on the workflows, and runs again on each push tomain(see Security linting). Both are required checks. - Git hooks (lefthook): on commit,
mise run checkwhen a TypeScript file or atsconfig.jsonunderactions/is staged. On push,mise run testwhen an action'ssrc/orpackage.jsonchanged, andmise run check-distwhen itssrc/,dist/,package.json,package-lock.jsonortsconfig.jsonchanged. - Every Monday: Dependency audit runs
pnpm auditon the root lockfile andnpm auditin each Node action, and keeps an issue labeleddependency-auditopen while it finds known vulnerabilities.
Every Node action in this repository has the same shape:
action.ymldeclaresruns.using: node24andmain: dist/index.js.- The source is in
src/. The bundle,dist/index.js, is committed to the repository and runs as is on the GitHub Actions runner: no Docker build, and no install at run time. mise.tomldeclares abuildtask that bundlessrc/index.tsintodist/with ncc, minified and with the dependency licenses indist/licenses.txt. mise tracks itssourcesandoutputsand skips the build when nothing changed, somise run builddoes not catch a hand-editeddist/. The rootcheck-disttask does: it rebuilds everydist/with--force, and fails if anything underdist/differs from the commit, new files included.- The lefthook
pre-pushhook runsmise run check-distfor any push that touches an action'ssrc/,dist/,package.json,package-lock.jsonortsconfig.json, and blocks the push whendist/is out of sync withsrc/. - CI (Check actions dist sync) runs
check-diston every pull request. It is a required check, so a push that skips the local hook is still caught. - Unit tests sit next to the code as
src/**/*.test.ts, and run with Node's built-in test runner (node --test, with native type stripping and no extra dependencies):mise run testin an action or at the root. The pre-push hook and Check actions dist sync run them too. preview-releaseandmonorepo-preview-releasehold byte-identical copies of the comment-mode code (src/comment.ts,src/types.ts) and of the shared tests (src/comment.test.ts,src/core.test.ts). Change both copies together: CI diffs them.
To add a Node action, copy the structure of monorepo-preview-release or railway-redeploy, change the inputs and the logic, and run mise run build. mise run check-dist works too: the pre-push hook runs it, and it tells you to commit the change to dist/.
A new dependency version has to be at least 24 hours old before it gets in. Most malicious releases are pulled within the first hour.
-
Root tooling (pnpm):
pnpm-workspace.yamlsetsminimumReleaseAge: 1440, except for@variablelab/*and@rrlab/*. pnpm 10.16 or later, pinned inpackage.jsonandmise.toml, refuses a version that is too new withERR_PNPM_NO_MATURE_MATCHING_VERSION. -
Action dependencies (npm, one
package-lock.jsonper action): npm 11.10 and later have a lasting cooldown,min-release-age, but the actions don't set it. When you add or update a dependency of an action, pass--beforewith a time 24 hours in the past, so npm resolves only versions published before it:cd actions/<action> npm install <pkg>@<range> --before="$(node -p 'new Date(Date.now() - 864e5).toISOString()')"
The same flag works with
npm update. Then runmise run check-dist, and commit the rebuiltdist/with the lockfile. -
The global npm CLI in
setup-pnpmandsetup-pnpm-bun: an exact version, bumped by hand to a release at least 24 hours old (see the comment on the "Update npm" step). Both actions are deprecated in favor of mise, which uses the npm bundled with the locked Node. -
Security updates: Dependabot security updates are on in the repository settings, and
dependabot.ymlgroups them, so Dependabot opens grouped pull requests for the security alerts at the root and in each action, not one pull request per alert. Version updates stay manual. Dependency audit audits every lockfile each Monday, and keeps an issue open while advisories remain. Dependabot doesn't rebuilddist/: on its pull requests for an action, Check actions dist sync fails until someone runsmise run check-diston the branch and pushes the rebuiltdist/. Its pull requests also skip the 24-hour cooldown, because GitHub ignorescooldownfor security updates and Dependabot runs pnpm withminimumReleaseAge=0. Check the fixed version's publish date before you merge one.
Workflow audit (workflow-audit.yml) runs zizmor on every pull request and every push to main, with the config in .github/zizmor.yml. Third-party actions are pinned by SHA, and withvariablelab/* is allowed at @main. To run it locally: pipx run --spec zizmor==1.30.1 zizmor ..