Hosted cache
bento’s cache is content-addressed: each task is keyed by its inputs, toolchain, env, and command. The hosted cache at cache.bento.build is a remote tier for those keys — your laptop, your teammate’s laptop, and CI all read and write the same store, so work that has already been done anywhere doesn’t get done again.
It’s the same bento:// wire protocol the CLI speaks to any compatible server, so nothing here locks you in. If you’d rather run your own store, see Self-hosting on S3.
1. Sign in
Section titled “1. Sign in”bento loginbento login runs a device-code flow — no flags, no config to edit first:
To authorize this CLI, open: https://app.bento.build/cli-login?user_code=WXYZ-1234Device code: WXYZ-1234Waiting for approval (15 min)…Open the URL, approve the device, and the CLI stores the returned JWT for you:
Logged in. Token stored in OS keychain.Add --agent (read-only, one hour) when the session belongs to a coding agent rather than to you; --scope and --ttl set those two separately. On a headless or keychain-less host it falls back to ~/.bento/credentials (mode 0600) and says so. Re-running bento login overwrites the stored token; there is no bento logout — revoke from the dashboard instead (see Tokens).
2. Point bento.toml at it
Section titled “2. Point bento.toml at it”[cache]remote = "bento://cache.bento.build"remote_token_env = "BENTO_CACHE_TOKEN"local = true is already the default, so the local tier stays on and the remote is a second tier behind it. remote_token_env names the environment variable that holds the JWT — it is not the token itself, and it’s safe to commit. bento doctor fails the cache.remote check if a bento:// remote is configured without it.
Credential resolution, first non-empty wins:
$BENTO_CACHE_TOKEN(or whateverremote_token_envnames)- the OS keychain entry
bento/cache-token, written bybento login ~/.bento/credentials
So an interactive dev never exports anything, and CI never needs a keychain.
Full field reference: Configuration › [cache].
3. Wire CI
Section titled “3. Wire CI”The token is a normal secret. On GitHub Actions:
jobs: ci: runs-on: ubuntu-latest env: BENTO_CACHE_TOKEN: ${{ secrets.BENTO_CACHE_TOKEN }} steps: - uses: actions/checkout@v4 with: { fetch-depth: 0 } with: version: '0.1.2' task: civersion makes the action download the prebuilt binary instead of compiling from source; omit it and the step falls back to cargo install.
Add the secret once with gh secret set BENTO_CACHE_TOKEN -R <owner>/<repo>, pasting a token minted for CI — a read_write-scoped one, not your personal login. See Tokens.
Two cache tiers are active inside Actions: the GitHub Actions cache ([cache] gha = "auto", on by default in a workflow) and the remote. The GHA tier is per-repo and evicted aggressively; the remote is shared across repos, branches, and your laptops.
What a hit looks like
Section titled “What a hit looks like”bento: web (3 dishes)
marketing (bun) build [cache hit ] 12ms lint [cache hit ] 9ms api (go) build [built ] 2841ms
summary: 3 dishes · 3 tasks · 1 built · 2 cached · 0 failed · 2863mscache hit means the task’s outputs were restored instead of run. With --json, the same rows carry "kind": "cache_hit" and the summary carries hits. Piped --json is compact; on a terminal it’s pretty-printed.
One honest caveat: the output doesn’t say which tier served the hit. A restore from the local cache and a restore from cache.bento.build render identically. To confirm the remote is actually being used, watch the dashboard’s usage page, or clear the local tier (bento cache clear) and re-run — a second-run hit after a clear can only have come from the remote.
Verifying the wiring
Section titled “Verifying the wiring”bento doctor --cloud--cloud is the only networked part of doctor (everything else is local), and adds:
| Check | What it does |
|---|---|
cloud.token.env | the env var named by remote_token_env is set and non-empty |
cloud.token.claims | decodes the JWT payload — iss, team_id, scope, label |
cloud.token.expiry | fails once exp has passed, telling you to re-mint |
cloud.cache.health | GET https://cache.bento.build/health |
cloud.api.health | GET https://api.bento.build/v1/healthz |
If those probes fail and you want to know whether it’s you or us, /status reports the same rollup the control plane serves at GET /v1/status — and remember that a degraded edge means slower builds, never failed ones.
Note that cloud.token.env only reads the environment variable. If you signed in with bento login and the token lives in the keychain, this check reports it unset even though builds work — same for the remote: line in bento prime.
Digging into a key
Section titled “Digging into a key”bento why marketing:buildbento why prints the stored input manifest behind a cache key: every hashed file with its blake3 digest and size, the env var names that participate (values are hashed, never displayed), the resolved toolchain, the command, and the bento + adapter version stamps. It takes either <dish>:<task> or any hex prefix from bento plan --json / bento ci --json.
It answers “what goes into this key”, which is what you need when a task rebuilt and you expected it not to. For the hit/miss verdict itself, use bento plan — each task comes back cache_hit, cache_miss (with a miss_reason), no_adapter, or skipped_diff_clean.
How long entries live
Section titled “How long entries live”Entries age out. Your plan carries a retention window — 14 days on free, 90 on starter, a year on team — and an hourly sweep deletes anything in your team’s cache that’s older than it. Reading a key doesn’t extend it; the window runs from the upload.
That’s deliberate, and it’s cheap: an evicted key that’s needed again is a cache miss, so the task runs once at cold speed and re-uploads its output. Nothing breaks, nothing is unrecoverable, and any key your builds actually keep producing stays in the cache — a task that still runs re-uploads its output, which restarts the clock. What ages out is the long tail nobody has asked for.
The other half of this is the quota, which caps stored bytes rather than monthly uploads — see Usage & billing. Retention is what keeps that number from only ever going up.
Moving entries by hand
Section titled “Moving entries by hand”bento cache stats # entries, total size, oldest/newest, per-tier locationbento cache push # force-push local entries to the remotebento cache pull # force-pull remote entries down to localbento cache clear # wipe the local tierNone of these take flags beyond the globals (--json, --workspace, -v). push and pull print ✓ push: 41 transferred · 3 skipped · 0 failed (remote: bento://cache.bento.build) and exit 1 if anything failed. pull reconciles only keys this workspace already knows about — it is not a “download the whole cache” button.
Read-through, write-back, and failure
Section titled “Read-through, write-back, and failure”Reads go local first, then remote. A local hit returns without touching the network — so a warm machine doesn’t pay a round trip per task. Writes go back to the remote after a successful build.
A network error never fails your build. If cache.bento.build is unreachable, bento logs it and runs the task.
If you want a local hit to also backfill the remote (useful on the one machine that warms the cache for everyone else):
[cache]remote_write_through = trueOff by default, matching Turborepo and Nx: local is the fast lane.
Build reports
Section titled “Build reports”With a bento:// remote configured, bento ci and bento build POST a small build report to it: package, branch, sha, cache_hit_ratio, status, duration_ms. That’s what fills the dashboard’s build history. No PII, no env values, no command lines. It’s best-effort — a failure is logged, never fatal.
Opt out with [telemetry] enabled = false in bento.toml (team-wide, committed) or BENTO_TELEMETRY=0 (per machine). Either one turns it off; the env var can’t turn it back on. With no bento:// remote, nothing is ever sent. bento doctor reports the resolved posture as telemetry.posture.