Skip to content

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.

Terminal window
bento login

bento 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-1234
Device code: WXYZ-1234
Waiting 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).

bento.toml
[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:

  1. $BENTO_CACHE_TOKEN (or whatever remote_token_env names)
  2. the OS keychain entry bento / cache-token, written by bento login
  3. ~/.bento/credentials

So an interactive dev never exports anything, and CI never needs a keychain.

Full field reference: Configuration › [cache].

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 }
- uses: bento-sh/[email protected]
with:
version: '0.1.2'
task: ci

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

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 · 2863ms

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

Terminal window
bento doctor --cloud

--cloud is the only networked part of doctor (everything else is local), and adds:

CheckWhat it does
cloud.token.envthe env var named by remote_token_env is set and non-empty
cloud.token.claimsdecodes the JWT payload — iss, team_id, scope, label
cloud.token.expiryfails once exp has passed, telling you to re-mint
cloud.cache.healthGET https://cache.bento.build/health
cloud.api.healthGET 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.

Terminal window
bento why marketing:build

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

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.

Terminal window
bento cache stats # entries, total size, oldest/newest, per-tier location
bento cache push # force-push local entries to the remote
bento cache pull # force-pull remote entries down to local
bento cache clear # wipe the local tier

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

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 = true

Off by default, matching Turborepo and Nx: local is the fast lane.

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.