Skip to content

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.

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.toml and .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.

  • Team tier license (canopy license --show should display tier: team)
  • Team ID (found in the Canopy admin portal at admin.canopy.ironpinelabs.com)
  • CANOPY_LICENSE_KEY stored as a CI secret (GitHub Actions) or CI/CD variable (GitLab)

The simplest way to use remote cache is to pass --team to canopy ci. Canopy handles upload and restoration automatically:

Terminal window
canopy ci --team <team_id> --format github --fail-on-p0

No 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.

For more control, use --cache-to and --use-cache explicitly:

Upload after indexing:

Terminal window
canopy index . --with-search --with-git \
--cache-to r2://<team_id>/<repo-hash>

Restore before indexing:

Terminal window
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-p0

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 required
canopy ci: indexing 8,432 files (47.2s)
canopy ci: uploading to remote cache (187 MB, 3.1s)
canopy ci: running health checks...

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).

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:

Terminal window
curl -fsSL https://downloads.canopy.ironpinelabs.com/releases/v1.4.0/forge-linux-x86_64 \
-o canopy

Cache 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.