A collection of reusable workflows for GitHub Actions.
The four install-capable workflows (test.yml, deploy.yml, release.yml, run-script.yml) declare these optional secrets:
VERDACCIO_TOKEN: auth token for the private Verdaccio registry (@willbooster-private/*). When set, the workflow generates git-excluded workspace registry configs before installing dependencies —.npmrcfor npm/bun/yarn1 and.yarnrc.ymlfor Yarn Berry (which ignores.npmrc), so Berry consumers no longer need to reference${VERDACCIO_TOKEN}from their committed.yarnrc.yml. Every caller should pass it. The generated configs stay on disk for the whole job, but the secret itself is step-scoped:${VERDACCIO_TOKEN}only expands in the "Install dependencies" step of all four workflows, pluscommon/ci-setupand "Deploy" indeploy.yml,common/ci-setupand "Run script" inrun-script.yml, "Release" inrelease.yml, and "Test release script" intest.yml. Consumer scripts running on any other step (e.g.test/ci-setup,cleanup,build, tests) see an emptyVERDACCIO_TOKENand must rely on the already-installed dependency graph instead of fetching from the private registry at run time.TAKUMI_GUARD_TOKEN: auth token for the Takumi Guard registry proxy, which blocks known-malicious packages. When set, the generated registry configs (.npmrcfor npm/bun/yarn1,.yarnrc.ymlfor Yarn Berry) route newly resolved public (default-registry) packages throughhttps://npm.flatt.tech/for every package manager;@willbooster-private/*packages keep resolving from Verdaccio withVERDACCIO_TOKEN. The token follows the same step-scoping asVERDACCIO_TOKEN. While either registry token is present, the generated.yarnrc.ymlreplaces the committed top-levelplugins:,npmRegistryServer:,npmRegistries:, andnpmScopes:blocks for the whole job, and the generated.npmrcreplaces the managedregistry=/scope lines: consumers must not rely on committed default-registry overrides, extra scopes, or third-party Berry plugins during CI. Independently of this secret, WillBooster's self-hosted runner machines carry a persistent~/.npmrc(and~/.yarnrc.yml) with Guard's public anonymous token, provisioned by self-host-utils, so default-registry installs on those machines route through the proxy even for callers that pass no token. Coverage: for bun, an EMPTYresolvedfield does not bypass the proxy — bun derives the download URL from the configured registry, so already-locked packages go through Guard too. A non-emptyresolvedis what bypasses it, and Yarn 1 always records one; Yarn Berry resolves through the proxy via the generated.yarnrc.yml(itsyarn.lockstores registry-agnosticnpm:resolutions, so no lockfile normalization is needed). Note the side effect for bun consumers: an install that UPDATESbun.lockunder any Guard-routed.npmrc(workspace or machine-level) makes bun rewrite every already-locked package'sresolvedto the proxy host, sotest.ymlnormalizes those URLs back to""regardless of whether this token is set — otherwise a committed lockfile would pin every environment to the proxy and fail cold-cache installs with 401 for anyone holding aregistry.npmjs.orgtoken. The normalization runs before the diff is reported, so it appears with the other fixer changes intest.yml's failure output. Publishing repositories must declarepublishConfig.registryexplicitly (npmjs for public packages — wbfy injects it; Verdaccio publishers already declare theirs): without it,npm publishresolves the Guard proxy from the generated workspace.npmrc— or from the machine-level~/.npmrcon tokenless self-hosted runs — and fails with 405 (Berry publishers likewise neednpmPublishRegistryorpublishConfig.registry, sinceyarn npm publishfalls back tonpmRegistryServer).FNOX_AGE_KEY: age secret key that decrypts the age-encrypted secrets committed in the caller'sfnox.toml. Required for repositories whosefnox.tomlcontains age-encrypted secrets; after mise installs fnox,deploy.yml,release.yml, andrun-script.ymlfail fast when the committed secrets cannot be resolved (missing or wrong key), whiletest.ymlonly warns (fork pull requests run it without secrets). Afnox.tomlwith only plaintext defaults needs no key, but a non-development job must still declare the selected profile (an inline[profiles.<name>]or afnox.<name>.tomlfile) — an undeclared profile silently falls back to the base (development) secrets, so the check rejects it.
Do NOT pass these secrets explicitly to the other workflows (e.g. semantic-pr.yml, close-comment.yml): GitHub rejects a secrets: map entry the callee does not declare (secrets: inherit is exempt from this validation). Running wbfy (>= 3.0.0) on the caller repository injects VERDACCIO_TOKEN/FNOX_AGE_KEY automatically.
test.yml never pushes fixes. Its test job runs the pull request's own dependencies, so its checkout persists no push credential (persist-credentials: false); when the fixers (gen-code, cleanup, lint-fix, build) change anything, the job fails with the diff, and a developer or agent applies it. The former App-based autofix-apply.yml flow (a workflow_run job committing an uploaded patch with a GitHub App token) was retired in 2026-07: committing CI-generated patches with a bot credential is a standing risk with little benefit now that agents fix and verify before pushing, and retiring it let the App private key and its token broker be decommissioned. The autofix.ci path for public repositories (autofix.yml) and the nightly self-applying wbfy.yml were retired at the same time, so this repository hosts no workflow that writes to a caller's repository at all.
The test.yml caller's permissions must NOT be narrowed just because test.yml never pushes or dispatches; the grants are needed for other reasons:
contents: write— the "Test release script" step runssemantic-release --dry-run, which performs agit push --dry-runto verify write access and aborts withEGITNOPERMISSIONwithout it, even in a dry run. Callers such asprompt-study,build-tsandagentic-workflowsreach that step.actions: write— the test job runsfkirc/skip-duplicate-actionswithcancel_others: true, which callscancelWorkflowRun. Dropping the grant also loses the impliedactions: read, so duplicate/paths_ignoreskipping silently stops working too.
Source checkouts use the automatically provided GITHUB_TOKEN (no secret needs to be passed, and self-hosted runners need no SSH deploy key). Exception: sync.yml pushes to the caller-supplied DEST_GIT_URL secret, so an SSH-form value there still needs runner SSH credentials until it is migrated to an HTTPS token URL.
Note: this repository is mirrored to WillBoosterLab/reusable-workflows with one-way-git-sync via the sync script, which maintainers run from their machines (bun run sync; renovate.jsonc and node_modules are excluded). The mirror is not synced automatically on merge, so it can lag main — run bun run sync after merging changes that WillBoosterLab callers need. The script passes -b main explicitly rather than relying on inference, and the pinned one-way-git-sync must stay at >= 6.0.45: earlier versions turned a transient clone failure into a push to a branch literally named undefined while still reporting success (and, with -b, into fatal: a branch named 'main' already exists). Verify the result by the mirror's own sync commit rather than the exit code — its title embeds the source commit it mirrored, so it should name the local main HEAD:
git rev-parse main
gh api repos/WillBoosterLab/reusable-workflows/commits/main --jq '.commit.message | split("\n")[0]'