Commands
Run shell commands on a job's machine, with retries that skip work that already passed.
Inngest CI is an Inngest Labs project: early, moving fast, and shaped by your feedback. What's Labs?
$ runs a command on the job's machine. A non-zero exit code throws CommandFailedError, which fails the job.
const test = ci.job("test", async () => {
await checkout();
await $`pnpm install`;
await $`pnpm --filter ${pkg} test`;
});
$ runs without a shell. The template's text is split on spaces and quotes are not parsed, so pass a value with spaces as an interpolation. Each interpolated value becomes one argument with no quoting, unless it follows text with no space, as in --filter=${pkg}, where it joins that argument. Arrays spread into several arguments, and false, null, and undefined are dropped. $.sh runs /bin/sh -c and escapes interpolated values, for pipes and &&.
Retries
.retries(n) runs a failed command up to n more times on the same machine. Each attempt is its own step in the trace.
await $`pnpm test`.retries(2);
Each command is a step. If the run resumes after a failure elsewhere, such as a GitHub API 503, Inngest replays the saved results and does not run finished commands again. Inngest does not retry a whole run because a command failed. If a command still fails after its retries, the job's check fails and the run ends.
Options
Options chain onto the command:
await $`pnpm lint`.nothrow();
await $`pnpm test`.env({ CI: "true" }).timeout("10m");
await $`pnpm exec playwright test`.as("e2e");
const sha = await $`git rev-parse HEAD`.text();
.nothrow()returns the result with its exit code instead of throwing..env(),.cwd(), and.timeout()set the environment, directory, and time limit..as(name)replaces the command text in the step name, as intest › e2e..text(),.lines(), and.json()return stdout in a convenient shape.
Reference lists every method, the result fields, and .background().
Share commands between jobs
Any function can run commands. It uses the calling job's machine:
export async function install() {
await checkout();
await $`pnpm install --frozen-lockfile`;
}
const test = ci.job("test", async () => {
await install();
await $`pnpm test`;
});
$ outside a job throws CiUsageError. Output arrives when the command ends, not while it runs.