Caching

Skip a job when nothing it depends on has changed, and restore its machine instead.

Inngest CI is an Inngest Labs project: early, moving fast, and shaped by your feedback. What's Labs?

A job's cache skips the job when its key is unchanged. A job that started a machine is restored from its snapshot, so from(base) clones the saved machine without running base again.

TypeScript
const base = ci.job(
  {
    id: "base",
    cache: {
      key: files("pnpm-lock.yaml", ".nvmrc"),
      refresh: [{ cron: "0 3 * * *" }],
    },
  },
  async () => {
    await checkout();
    await $`pnpm install`;
  },
);

A restored machine has the checkout from the commit it was built on. Jobs that start from it call checkout() again to move to this run's commit, and keep the installed dependencies:

TypeScript
const test = ci.job("test", async () => {
  await from(base);
  await checkout();
  await $`pnpm test`;
});

Choose the key

A key is files(), a string, an array of them, or an async function that returns a string. files() hashes the matched files in the git tree for the run's commit. Locally it hashes the working tree.

TypeScript
key: files("migrations/**", "seeds/**")
key: [files("go.mod", "go.sum"), "go1.25"]

A job's input is part of its cache identity, so the same key with a different input is a different entry. The first run of a job always misses. A cached job without a machine is skipped, and a reused job shows as passed on its check.

Chains of cached jobs

A cached job also depends on the cached jobs it starts from(). When a parent changes its key, or the input it was called with, the child's entry is stale and the child runs again, through the whole chain of cached parents.

An uncached parent has no key and never invalidates its children. Add its files to the child's key when the child depends on them.

Refresh ahead of time

refresh takes triggers that rebuild the cache on a schedule, so pull requests do not pay for it. Set repo on the pipeline so a cron has a repository to check out. scope is "branch" by default: a pull request reads entries from its base branch and writes its own, and its scope can never collide with a branch name. "global" shares one entry set.

Do not cache tests that call the network.

Next steps