CI Cached Indexes (Team Tier)
canopy ci runs the full pipeline in one command: index, health check, annotations, non-zero exit on P0 findings. It requires a Pro tier license or higher. On large repos, the index step is the slow part of a cold run — the Team tier index cache stores the built index after the first run and restores it on subsequent runs, cutting index time from minutes to seconds when the commit hasn’t changed.
Two cache modes are available, both Team tier:
- Managed R2 — pass
--team <team_id>tocanopy ci; Canopy handles the cache slot automatically. - BYOB S3 — bring your own bucket (AWS S3, Cloudflare R2, GCS interop, MinIO). Your bucket, your keys; your code never proxies through GST infrastructure.
How the cache key works
Section titled “How the cache key works”The cache key is a SHA-256 over four inputs: the repo’s git HEAD SHA, the Canopy binary version, the hash of the effective .canopy/config.toml, and the index shape (with/without summaries, summary backend and model). Any input changing produces a different key, so cache hits are bit-for-bit safe — no silent drift between index shapes or binary versions.
The cached blob is integrity-checked on restore. A cache miss, a failed fetch, or a failed integrity check falls through to a normal full index; cache push failures are non-fatal. Caching is best-effort, never a hard prerequisite.
Managed R2: canopy ci --team
Section titled “Managed R2: canopy ci --team”Pass --team and Canopy computes the cache slot from the team ID and repo, restores on a hit, and uploads the rebuilt index after the run:
canopy ci --team <team_id> --format github --fail-on-p0name: Canopy CI
on: pull_request: branches: [main] push: branches: [main]
jobs: canopy-ci: runs-on: ubuntu-latest permissions: pull-requests: write checks: write
steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # full history for git-based insights
- name: Install Canopy run: | mkdir -p "$HOME/.local/bin" curl -fsSL https://downloads.canopy.gulfshieldtech.com/releases/latest/canopy-x86_64-unknown-linux-gnu.tar.gz \ | tar -xz -C "$HOME/.local/bin" echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Activate license env: CANOPY_LICENSE_KEY: ${{ secrets.CANOPY_LICENSE_KEY }} run: canopy activate "$CANOPY_LICENSE_KEY"
- name: Canopy CI with team cache run: canopy ci --team ${{ vars.CANOPY_TEAM_ID }} --format github --fail-on-p0Store CANOPY_TEAM_ID as a repository variable (it’s an ID, not a secret) and CANOPY_LICENSE_KEY as a secret.
canopy-ci: stage: test image: ubuntu:22.04
before_script: - apt-get update -qq && apt-get install -y -qq curl ca-certificates - curl -fsSL https://downloads.canopy.gulfshieldtech.com/releases/latest/canopy-x86_64-unknown-linux-gnu.tar.gz | tar -xz -C /usr/local/bin - canopy activate "$CANOPY_LICENSE_KEY"
script: - canopy ci --team "$CANOPY_TEAM_ID" --format gitlab --fail-on-p0 > canopy-report.json
artifacts: reports: codequality: canopy-report.json when: alwaysSet CANOPY_TEAM_ID as a CI/CD variable and CANOPY_LICENSE_KEY as a masked variable.
Useful controls:
--no-cache— skip the cache download and force a full rebuild; the result is still uploaded afterward.--clear-cache— delete the cached slot first, then rebuild and re-upload. Requires--team. Use when you know the cache is stale or corrupt.--with-summaries— builds the Tier-A summary index too; the cache slot is partitioned by summary backend and model, so a summaries-on run never picks up a summaries-off blob (or vice versa).--changed-only— restrict findings to files changed in the current PR.
BYOB: your own S3-compatible bucket
Section titled “BYOB: your own S3-compatible bucket”Configure the cache backend once per repo:
canopy setup --only cacheThe cache step walks you through backend choice, endpoint, bucket, region, and the names of the credential environment variables, then writes a [cache] block to .canopy/config.toml:
[cache]backend = "s3"endpoint = "https://<account>.r2.cloudflarestorage.com"bucket = "canopy-ci-cache"region = "auto"access_key_env = "CANOPY_CACHE_ACCESS_KEY"secret_key_env = "CANOPY_CACHE_SECRET_KEY"prefix = "indexes/"Export the credentials and verify before relying on it in CI:
export CANOPY_CACHE_ACCESS_KEY=<your-access-key>export CANOPY_CACHE_SECRET_KEY=<your-secret-key>canopy cache verifycanopy cache verify issues a HEAD probe against a synthetic key — it proves auth and bucket access without uploading anything, and exits 1 with a specific hint on missing credentials, 403s, or a missing bucket.
In CI, use the bare --use-cache / --cache-to flags on canopy index; with no URI argument they read the [cache] config and derive the key automatically:
- name: Index with BYOB cache env: CANOPY_CACHE_ACCESS_KEY: ${{ secrets.CANOPY_CACHE_ACCESS_KEY }} CANOPY_CACHE_SECRET_KEY: ${{ secrets.CANOPY_CACHE_SECRET_KEY }} run: canopy index . --use-cache --cache-to
- name: Health check run: canopy ci --format github --fail-on-p0--use-cache skips the rebuild entirely on a hit; --cache-to publishes the fresh blob after a rebuild so the next run hits.
Common pitfalls
Section titled “Common pitfalls”Cache never hits
The cache key includes the Canopy binary version. If CI downloads latest each run, a Canopy release turns every run into a miss. Pin the release in your install URL (replace latest with a specific version) for stable keys and reproducible builds.
Cache misses after config changes
Edits to .canopy/config.toml (ignored paths, health-check overrides, summary backend) change the config hash, which changes the key. The first run after a config change is a full index by design.
BYOB fetch fails in CI but canopy cache verify passes locally
The credential environment variables named in [cache] must be set in the CI job’s environment. Add them as CI secrets and pass them to the index step.