CI Cached Indexes (Team Tier)
On large repos, a fresh canopy index in CI can take 60–120 seconds. Team tier includes a remote cache that stores the index after the first build and restores it on subsequent runs. When only a small number of files change between commits, the restore + incremental update is typically under 5 seconds.
How the cache works
Section titled “How the cache works”The cache key is a hash of three inputs:
- The git commit SHA of the repo being indexed
- The Canopy binary version
- The hash of
.canopy/config.tomland.canopy/team.yml
When these three inputs match a stored cache, Canopy skips re-indexing entirely and restores the stored index directly. When files change between commits, Canopy restores the closest ancestor cache and runs an incremental update — only changed files are re-parsed.
Cache integrity is verified automatically. A corrupted or tampered cache is rejected and Canopy falls back to a full index.
Prerequisites
Section titled “Prerequisites”- Team tier license (
canopy license --showshould displaytier: team) - Team ID (found in the Canopy admin portal at
admin.canopy.ironpinelabs.com) CANOPY_LICENSE_KEYstored as a CI secret (GitHub Actions) or CI/CD variable (GitLab)
Simple approach: canopy ci --team
Section titled “Simple approach: canopy ci --team”The simplest way to use remote cache is to pass --team to canopy ci. Canopy handles upload and restoration automatically:
canopy ci --team <team_id> --format github --fail-on-p0No separate canopy index step needed. Canopy checks the remote cache, restores if available, runs an incremental update if needed, runs health checks, and uploads the updated index back to the cache.
name: Canopy Health Check
on: pull_request: branches: [main] push: branches: [main]
jobs: forge-health: runs-on: ubuntu-latest permissions: pull-requests: write checks: write
steps: - uses: actions/checkout@v4 with: fetch-depth: 0
- name: Install Canopy run: | curl -fsSL https://downloads.canopy.ironpinelabs.com/releases/latest/forge-linux-x86_64 \ -o canopy && chmod +x canopy && sudo mv canopy /usr/local/bin/canopy
- name: Run Canopy CI with remote cache run: canopy ci --team ${{ vars.FORGE_TEAM_ID }} --format github --fail-on-p0 env: CANOPY_LICENSE_KEY: ${{ secrets.CANOPY_LICENSE_KEY }}Store FORGE_TEAM_ID as a repository variable (not a secret — it’s not sensitive) and CANOPY_LICENSE_KEY as a secret.
forge-health: stage: quality image: ubuntu:22.04
before_script: - apt-get update -qq && apt-get install -y -qq curl - curl -fsSL https://downloads.canopy.ironpinelabs.com/releases/latest/forge-linux-x86_64 -o /usr/local/bin/canopy - chmod +x /usr/local/bin/canopy
script: - canopy ci --team $FORGE_TEAM_ID --format gitlab --fail-on-p0 | tee forge-report.json - exit ${PIPESTATUS[0]}
artifacts: reports: codequality: forge-report.json when: alwaysSet FORGE_TEAM_ID as a CI/CD variable (unmasked — not sensitive) and CANOPY_LICENSE_KEY as a masked variable.
Manual cache control
Section titled “Manual cache control”For more control, use --cache-to and --use-cache explicitly:
Upload after indexing:
canopy index . --with-search --with-git \ --cache-to r2://<team_id>/<repo-hash>Restore before indexing:
canopy index . --with-search --with-git \ --use-cache r2://<team_id>/<repo-hash><repo-hash> is a stable identifier for the repository — typically $(git remote get-url origin | sha256sum | cut -c1-16) to make it consistent across runners.
Full workflow using explicit cache control:
- name: Restore Canopy index from remote cache run: | REPO_HASH=$(git remote get-url origin | sha256sum | cut -c1-16) canopy index . --with-search --with-git \ --use-cache r2://${{ vars.FORGE_TEAM_ID }}/${REPO_HASH} || true # || true: if no cache exists (first run), fall through to full index
- name: Full index if cache miss run: canopy index . --with-search --with-git
- name: Upload index to remote cache run: | REPO_HASH=$(git remote get-url origin | sha256sum | cut -c1-16) canopy index . --cache-to r2://${{ vars.FORGE_TEAM_ID }}/${REPO_HASH}
- name: Run health check run: canopy ci --repo . --format github --fail-on-p0Cache hit statistics
Section titled “Cache hit statistics”When --team is used, Canopy logs cache performance to stdout:
canopy ci: checking remote cache...canopy ci: cache HIT (key: abc123def456, age: 4h32m)canopy ci: restoring index (187 MB, 2.3s)canopy ci: incremental update (14 files changed, 0.8s)canopy ci: running health checks...A cold miss (no cache available):
canopy ci: checking remote cache...canopy ci: cache MISS — full index requiredcanopy ci: indexing 8,432 files (47.2s)canopy ci: uploading to remote cache (187 MB, 3.1s)canopy ci: running health checks...Cache retention
Section titled “Cache retention”Remote cache entries expire after 30 days of no access. Active repos (daily CI runs) never expire in practice. Manually purge a cache entry from the admin portal if you need to force a full re-index (e.g., after significant refactoring that breaks incremental updates).
Common pitfalls
Section titled “Common pitfalls”Cache never hits
The cache key includes the Canopy binary version. If your CI downloads latest each run and Canopy releases a patch, the version changes and every run is a cache miss. Pin to a specific version:
curl -fsSL https://downloads.canopy.ironpinelabs.com/releases/v1.4.0/forge-linux-x86_64 \ -o canopyCache hit but index seems stale
Verify that fetch-depth: 0 is set in the checkout step. With shallow clones (fetch-depth: 1), canopy index --with-git can’t read full git history, which affects the git activity data in canopy_prepare results. It doesn’t affect the cache key — that’s based on commit SHA, not history depth.
“Team not found” error
The team ID in FORGE_TEAM_ID doesn’t match your account. Log in to admin.canopy.ironpinelabs.com and copy the team ID exactly as shown.