Back to Zeroclaw

CI & Actions

docs/book/src/maintainers/ci-and-actions.md

0.8.315.4 KB
Original Source

CI & Actions

Every workflow lives in .github/workflows/. The sections below group them by trigger: automatic on git events, or maintainer-invoked/advisory workflows via workflow_dispatch and schedules.

Automatic workflows

Quality Gate (ci.yml)

Fires on every PR targeting master and on trusted pushes to master. Composite job with multiple matrix legs:

  • fmt: cargo fmt --all -- --check
  • lint: cargo clippy --workspace --exclude zeroclaw-desktop --all-targets --features ci-all -- -D warnings, plus two architecture guards (cargo test --test architecture): config-write isolation and Fluent coverage (no bare user-facing strings)
  • build: matrix: x86_64-unknown-linux-gnu, aarch64-apple-darwin, x86_64-pc-windows-msvc
  • check: all features + no-default-features
  • check-32bit: i686-unknown-linux-gnu with no default features
  • bench: benchmarks compile check
  • test: cargo nextest run --locked --workspace --exclude zeroclaw-desktop on Linux
  • security: cargo deny check
  • nix-eval: evaluates the NixOS module assertions (nixos-module-eval flake check)
  • docs-style: markdown lint, em-dash prose check, and changed-line link gate via scripts/ci/docs_quality_gate.sh and scripts/ci/docs_links_gate.sh

fmt runs first as the cheap serial gate. Every other job declares needs: [fmt] and fans out after formatting passes; CI Required Gate aggregates every result. Branch protection pins the composite gate job. A PR cannot merge until this is green. The master push run keeps the same quality signal while seeding trusted Rust caches for later PR runs.

Fresh required CI is normally the shared evidence for the Cargo surfaces it actually runs. A local rerun of the same Cargo command on the same head, target, and feature set is duplicate confidence, not a stronger proof. Before asking for extra Cargo or Clippy, compare the changed surface with the current workflow files and the actual checks on the PR. Extra validation belongs where the required gate does not prove the thing under review:

  • a platform received compile checks but not tests;
  • a platform, crate, or path is outside the required lint job;
  • a desktop change did not trigger the desktop workflow;
  • a release target is outside the PR matrix and only covered by release/manual workflows;
  • stale, cancelled, skipped, or unavailable CI is not fresh evidence.

Daily Advisory Scan (daily-audit.yml)

Runs cargo deny check advisories daily at 09:00 UTC against the dependency tree. Opens an issue on findings. No action unless a vulnerability is reported.

Daily npm Audit (daily-npm-audit.yml)

Runs npm audit --audit-level=high daily at 09:23 UTC against web/package-lock.json. Opens one deduplicated security + dependencies issue when high-severity npm advisories affect the committed web lockfile.

PR Path Labeler (pr-path-labeler.yml)

Auto-applies path and scope labels based on changed files. It runs on PR open, reopen, and every pushed update to the PR branch. Because sync-labels: true is enabled, labels defined in .github/labeler.yml are recalculated from the current PR file set.

This workflow does not currently apply risk:*, size:*, type:*, contributor-tier, status, resolution, stale, or pickup labels. If a PR is missing a path/scope label, check whether the paths in .github/labeler.yml cover the changes.

Dependabot has separate label configuration in .github/dependabot.yml for its own PRs. Cargo update PRs start with dependencies; GitHub Actions and Docker update PRs start with ci and dependencies.

Project Dashboard Planner (project-dashboard-plan.yml)

Runs manually for a single issue number. It reads issue state and labels, then writes a report-only step summary proposing the existing Project Status value that best matches the issue.

This workflow does not run automatically on issue events, write ProjectV2 fields, edit issues, add labels, post comments, or recalculate PR risk:*, size:*, or type:* labels. Live ProjectV2 mutation or automatic issue-event planning needs a separately approved field mapping, trigger policy, and project-scoped credential.

Validate PR title (pr-title.yml)

Runs on every PR open/edit/synchronize. Runs the validator unit tests (scripts/check-pr-title.test.sh) and checks the PR title against Conventional Commits (scripts/check-pr-title.sh).

Deploy mdBook docs to Pages (docs-deploy.yml)

Triggered on tag push (and workflow_dispatch); builds and publishes versioned docs to the gh-pages branch. See Release Runbook → Versioned documentation deployment for the version-floor and bootstrap rules.

Docker Image PR Check (docker-image-pr.yml)

Runs only when Docker image or release-Docker context files change. It prepares a smoke docker-ctx with the same helper used by the stable release workflow, then builds the default prebuilt image and the Debian compatibility prebuilt image from Dockerfile.ci without pushing either image. This catches image dependency and COPY path breakage before release without giving PR runs registry write permission or running on every PR.

Discord Release (discord-release.yml)

Fires after a successful stable release. Posts the release notes to the community Discord.

Tweet Release (tweet-release.yml)

Fires after a successful stable release. Posts an announcement tweet.

Docs are built and published as part of the release pipeline rather than on every master push. Translation is a local-only workflow: run cargo mdbook sync --provider <name> for dedicated translation-cache PRs, new locales, and release translation passes. Routine English docs PRs may defer broad generated .po churn. See Docs & Translations for details.

Manual and Advisory Workflows

Monthly Outdated Scan (monthly-outdated.yml)

Scheduled monthly scan on the 1st of every month at 09:00 UTC. Runs cargo outdated --workspace across all workspace members. Opens a dependencies-labeled issue when stale deps are found. Permissions: contents: read + issues: write. Dedup guard prevents piling up if the previous issue is still open.

First triage step for a new issue: check if the reported outdated crates have semver-incompatible bumps and whether the consuming crate's API changed. If the bump is trivial (patch/minor), create a short dep-only PR. If the upgrade is blocked by semver breaks, close the issue with a note and the blocking crate name.

Cross-Platform Build (cross-platform-build-manual.yml)

Manual trigger for building release binaries across the full target matrix: Linux x86_64/aarch64 GNU plus armv7 and arm hard-float, macOS Intel/ARM, Windows x86_64, and aarch64-linux-android (built with the NDK). Use this to verify a branch compiles cleanly on non-Linux targets before tagging.

Cross-Platform Clippy (cross-platform-clippy.yml)

Manual and weekly scheduled advisory lint coverage on macOS aarch64 and Windows x86_64 targets. It mirrors the required PR lint command with --target set for each platform, but intentionally does not run on PRs and is not part of CI Required Gate.

Release Stable (release-stable-manual.yml)

Manual trigger for the full release pipeline. Builds all targets, creates the GitHub Release, pushes Docker images to GHCR, triggers the website redeploy, and invokes the distribution sub-workflows (Scoop, AUR, Homebrew, Discord, tweet). Two environment gates require maintainer approval mid-run: github-releases (the publish job) and docker.

See the Release Runbook for the full procedure.

Package Publishers

Each fires on workflow_dispatch with a version input. They are also invoked from the release workflow after a successful publish.

WorkflowWhat it does
pub-aur.ymlUpdates the Arch User Repository PKGBUILD and pushes to the AUR
pub-homebrew-core.ymlOpens a PR against homebrew/homebrew-core with the new version
pub-scoop.ymlUpdates the Scoop manifest for Windows

Required secrets

SecretUsed by
AUR_SSH_KEYpub-aur.yml
DISCORD_WEBHOOK_URLdiscord-release.yml
TWITTER_ACCESS_TOKEN, TWITTER_ACCESS_TOKEN_SECRET, TWITTER_CONSUMER_API_KEY, TWITTER_CONSUMER_API_SECRET_KEYtweet-release.yml
HOMEBREW_CORE_BOT_TOKEN, HOMEBREW_UPSTREAM_PR_TOKENpub-homebrew-core.yml
SCOOP_BUCKET_TOKENpub-scoop.yml
WEBSITE_REPO_PATrelease-stable-manual.yml (triggers the website repo redeploy)
GITHUB_TOKEN (automatic)All workflows that push commits, open PRs, or push images to GHCR

Docker images push to GHCR using the automatic GITHUB_TOKEN; there is no separate registry token. The release pipeline does not publish to crates.io, so no CARGO_REGISTRY_TOKEN is required.

Build cache behavior

Most Rust-heavy jobs in ci.yml use Swatinem/rust-cache@v2. The fmt, nix-eval, and docs-style jobs (none of which compile the workspace) do not. These behaviors are worth knowing when triaging cache-related flakes:

  • Cache writes are master-only. save-if is conditioned on github.ref == 'refs/heads/master', so PR runs read the master-seeded cache but never update it. PR branches can't pollute the shared cache with branch-specific artifacts. The push trigger on master is what gives the workflow a trusted cache-writing run after merges.
  • Cache saves on failure. cache-on-failure: true is set on every job, so a partial run still seeds the next attempt warm.
  • Windows build cache is enabled. The Windows build leg runs the same pinned Rust cache action as Linux and macOS. If Windows cache behavior flakes or regresses, revert the workflow change and document the failing restore/save evidence in the cache issue.
  • Incremental compilation is disabled. CARGO_INCREMENTAL: 0 at the workflow level. Incremental builds inflate cache size and produce non-reproducible artifacts under partial-stale conditions.
  • cargo-deny and cargo-nextest are installed fresh each run. The security job runs cargo install cargo-deny --locked; the test job pulls the cargo-nextest binary from get.nexte.st. Neither is cached, so both add a fixed install cost to every run. Switching either to taiki-e/install-action would let them be cached, but that action is not in the allowlist today.

When the gate goes red

SymptomFirst thing to check
CI Required Gate redStart with fmt, then lint, then test, then build
Release validate failedCargo.toml version doesn't match the workflow input, or the tag already exists
Release build leg failedThe specific target's job log. Android is experimental and runs with continue-on-error
Environment gate timed outRe-run only the timed-out job from the workflow run page
Distribution publisher failedRe-run the corresponding sub-workflow manually with dry_run: true first

Allowed actions

The repository runs Actions in selected mode, only the actions in this allowlist may run. The allowlist must stay tight; new third-party actions need explicit maintainer approval before being added.

All third-party refs are pinned to a full commit SHA with a trailing version comment; the version column below records that comment.

ActionUsed inPurpose
actions/checkout (v6.0.2)Most workflowsRepository checkout
actions/cache (v4.2.3, v5.0.5)docker-image-pr.yml, tweet-release.ymlGeneric dependency and Trivy database caching
actions/setup-node (v6.4.0)release-stable-manual.yml, cross-platform-build-manual.ymlNode toolchain for the web-dashboard build
actions/upload-artifact (v7.0.1)release-stable-manual.yml, cross-platform-build-manual.yml, docker-publish.ymlUpload build artifacts and Trivy SARIF handoff artifacts
actions/download-artifact (v8.0.1)release-stable-manual.yml, cross-platform-build-manual.yml, docker-publish.ymlDownload build artifacts and Trivy SARIF handoff artifacts
actions/labeler (v6.1.0)pr-path-labeler.ymlApply path/scope labels from .github/labeler.yml
dtolnay/rust-toolchain (stable)ci.yml, release-stable-manual.yml, cross-platform-build-manual.yml, cross-platform-clippy.yml, daily-audit.yml, docs-deploy.ymlInstall Rust toolchain
Swatinem/rust-cache (v2.9.1)ci.yml, release-stable-manual.yml, cross-platform-build-manual.yml, cross-platform-clippy.yml, docs-deploy.ymlCargo build/dependency caching
docker/setup-buildx-action (v4.0.0)release-stable-manual.ymlDocker Buildx setup
docker/login-action (v4.1.0)release-stable-manual.ymlGHCR authentication
docker/build-push-action (v7.1.0)release-stable-manual.ymlMulti-platform image build and push
sigstore/cosign-installer (v3.8.1)release-stable-manual.yml, docker-publish.ymlInstall cosign for keyless signing of release assets and container images
anchore/sbom-action (v0.17.9)release-stable-manual.ymlGenerate SPDX + CycloneDX SBOMs for each release
slsa-framework/slsa-github-generator (v2.1.0)release-stable-manual.ymlReusable workflow that produces SLSA L2 provenance for release artifacts
aquasecurity/trivy-action (v0.36.0)docker-image-pr.yml, docker-publish.ymlReport-only container vulnerability scanning
github/codeql-action/upload-sarif (v3.36.2)docker-publish.ymlUpload Trivy SARIF reports to the Security tab

The GitHub Release itself is created with gh release create inside the publish job, not a release action.

Equivalent allowlist patterns (kept narrow on purpose):

actions/*
dtolnay/rust-toolchain@*
Swatinem/rust-cache@*
docker/*
sigstore/cosign-installer@*
anchore/sbom-action@*
slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@*
aquasecurity/trivy-action@*
github/codeql-action/upload-sarif@*

Export the current effective policy:

<div class="os-tabs-src">

sh

sh
gh api repos/zeroclaw-labs/zeroclaw/actions/permissions
gh api repos/zeroclaw-labs/zeroclaw/actions/permissions/selected-actions
</div>

Any PR that adds or changes a uses: action source must include an allowlist impact note in its body. Avoid broad wildcard exceptions; expand the allowlist only for verified missing actions.

Maintenance rules

  • Keep CI Required Gate deterministic and small. Adding jobs to the gate needs a clear quality argument.
  • All third-party action refs must be pinned to a full commit SHA (per the allowlist policy above).
  • Keep ci.yml, dev/ci.sh, and .githooks/pre-push aligned, the same quality gates run locally and in CI.
  • Keep scripts/ci/prepare_docker_context.sh, docker-image-pr.yml, and the Docker job in release-stable-manual.yml aligned so PR validation exercises the same context shape the release workflow publishes.
  • The docs-style gate job runs bash scripts/ci/docs_quality_gate.sh (markdown lint + em-dash prose check) and bash scripts/ci/docs_links_gate.sh (changed-line link gate). Run both scripts locally before pushing docs changes.

Emergency rollback

If the allowlist locks out a critical action mid-incident:

  1. Temporarily set Actions policy back to all.
  2. Restore selected allowlist after identifying the missing entry.
  3. Record the incident and the final allowlist delta.

This is the only justified path to all mode, and it should never outlast the incident.