# Inngest CI overview

> Write your CI in TypeScript, run it on Inngest, and debug every run as one trace.

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

Inngest CI is CI written in TypeScript, in your repo next to your app. Pipelines start on any Inngest event, from a GitHub push to a cron or your own app, and each job runs on its own [Sandbox](/docs-markdown/sandboxes/overview?ref=docs-labs-ci-overview).

- **TypeScript all the way down.** Use `if`, loops, and `Promise.all`, and import your app's own code. [Pipelines →](/docs-markdown/labs/ci/pipelines?ref=docs-labs-ci-overview)
- **One trace per run.** Every job, command, and retry in one place. [See a run →](#see-the-result)
- **Retries that keep passed work.** Only the failed command runs again. [Retries →](/docs-markdown/labs/ci/commands?ref=docs-labs-ci-overview#retries)
- **Build once, reuse it.** Start jobs from another job's machine, and cache the slow ones. [Machines →](/docs-markdown/labs/ci/machines?ref=docs-labs-ci-overview#start-a-job-from-another-job)
- **Runs locally.** Test CI against your uncommitted changes. [Quick start →](/docs-markdown/labs/ci/quick-start?ref=docs-labs-ci-overview#5-run-locally)
- **Flow control.** Cancel stale runs, cap concurrency, and debounce. [Flow control →](/docs-markdown/labs/ci/pipelines?ref=docs-labs-ci-overview#flow-control)
- **GitHub checks.** One per pipeline, and one per job. [Checks →](/docs-markdown/labs/ci/checks-and-reports?ref=docs-labs-ci-overview)

## How a run works

This pipeline runs on every pull request. `base` installs dependencies once. `lint` and `test` each start from a copy of the `base` machine and run in parallel.

```typescript {{ title: "ci/pipelines.ts" }}
import { github, checkout, from, $ } from "@inngest/ci";
import { ci } from "./client";

export const pr = ci.pipeline(
  {
    id: "pr",
    on: github.pullRequest(),
    singleton: { key: "event.data.pull_request.number", mode: "cancel" },
  },
  async () => {
    await Promise.all([
      lint(),
      test(),
    ]);
  },
);

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

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

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

- **Run it on your machine** (line 2): The same pipeline runs against the Inngest Dev Server with your uncommitted changes, so you can check CI before you push. [Quick start](/docs-markdown/labs/ci/quick-start?ref=docs-labs-ci-overview#5-run-locally)

- **A pipeline is a function** (lines 4-16): A pipeline is TypeScript that lives in your repository, next to your business logic. It imports the same helpers and SDKs your app already uses. [Pipelines and triggers](/docs-markdown/labs/ci/pipelines?ref=docs-labs-ci-overview)

- **Typed triggers** (line 7): Choose the trigger in code and `event` is typed from it: the pull request number, branch and head commit, with autocomplete. [Triggers](/docs-markdown/labs/ci/pipelines?ref=docs-labs-ci-overview#triggers)

- **Flow control built in** (line 8): One line replaces concurrency groups and cancel-in-progress settings: a new push cancels the run still going for that pull request. Concurrency, throttling and debounce work the same way. [Flow control](/docs-markdown/labs/ci/pipelines?ref=docs-labs-ci-overview#flow-control)

- **Orchestrate with native JavaScript** (lines 11-14): No dependency graph to declare: `Promise.all` runs jobs in parallel, `await` runs them in order, and an `if` skips what a change does not need. [Jobs](/docs-markdown/labs/ci/jobs?ref=docs-labs-ci-overview)

- **Build once, share it** (lines 18-21): No cache keys to tune. `base` checks out and installs once, and every job that starts from it gets a copy of that machine. [Machines and from()](/docs-markdown/labs/ci/machines?ref=docs-labs-ci-overview)

- **Start where `base` finished** (lines 24, 29): Waits for the one `base` build, then starts this job on a copy of its machine, already checked out and installed. [Start a job from another job](/docs-markdown/labs/ci/machines?ref=docs-labs-ci-overview#start-a-job-from-another-job)

- **`lint`** (lines 23-26): Runs on its own copy of `base`, with no repeated checkout or install. [Jobs](/docs-markdown/labs/ci/jobs?ref=docs-labs-ci-overview)

- **Retries that keep passed work** (lines 28-31): If `pnpm test` fails it runs again on the same machine. Nothing that already passed runs again, so you never rerun all of CI. [Retries](/docs-markdown/labs/ci/commands?ref=docs-labs-ci-overview#retries)

- **One trace for the whole run** (lines 11-14): When both jobs pass the run completes, and its trace shows every job, command and retry in one place. [See the result](#see-the-result)

`base` runs once, however many jobs start from it. If `pnpm test` exits with a non-zero code, `.retries(1)` runs it again on the same machine, and each attempt is its own step in the trace. If it still fails, the `test` check fails and the run ends. Inngest does not retry a run because a command failed.

If a step fails for another reason, such as a GitHub API 503, Inngest retries that step and replays the saved results. `base`, `lint`, and every finished command do not run again.

## Concepts

A **pipeline** runs when a **trigger** fires and calls **jobs**. Each job runs **commands** on its own **machine** and reports a **check**. [Concepts](/docs-markdown/labs/ci/concepts?ref=docs-labs-ci-overview) defines each part with a short example.

## See the result

Every run opens in the Inngest dashboard as one trace. Its steps start with their job, as in `base › checkout`, and cover each command, the Sandbox steps behind them, and GitHub checks. [Step names](/docs-markdown/labs/ci/checks-and-reports?ref=docs-labs-ci-overview#read-the-trace) lists them.

![The trace of the example pr pipeline in the Inngest Dev Server: pipeline and job checks, cache lookups, then the base job creating its machine, checking out the repository and installing dependencies](/assets/docs/labs/ci/trace.png)

On GitHub, the same run appears as one check for the pipeline and one for each job:

```
✕ pr             test: `pnpm test` exited with 1
✓ pr / base      Passed in 41s
✓ pr / lint      Passed in 22s
✕ pr / test      `pnpm test` exited with 1
```

In dev mode, the same transitions print to your terminal. See [Checks and reports](/docs-markdown/labs/ci/checks-and-reports?ref=docs-labs-ci-overview).

## Next steps

- [Quick start](/docs-markdown/labs/ci/quick-start?ref=docs-labs-ci-overview) runs this pipeline on the Dev Server.
- [Concepts](/docs-markdown/labs/ci/concepts?ref=docs-labs-ci-overview) defines pipelines, triggers, jobs, commands, and machines.
- [Reference](/docs-markdown/labs/ci/reference?ref=docs-labs-ci-overview) lists every option, trigger, and command method.
- [Sandboxes overview](/docs-markdown/sandboxes/overview?ref=docs-labs-ci-overview) explains the machines that run each job.
- [Durable steps](/docs-markdown/durable-execution/primitives/step-run?ref=docs-labs-ci-overview) explains how Inngest saves and retries work.