CI reference
The options, steps, limits, commands and routes of CI on TanStack Source. To write a workflow step by step, start with Your first workflow.
Workflow files
- A workflow is a
.tsfile directly in.source/workflows/. Its default export isdefineWorkflow(...)with its jobs. - At plan time, a workflow file can import only
@tanstack-source/ci,node:built-ins, other files in.source/, andsrc:modules (Share steps). Import the code of your repository inside a step. - The forge plans in a container with no network and no secrets, for at most 30 s.
.source/can hold at most 2 MiB of files, and at most 500 files and folders.
defineWorkflow(name, options)
| Option | Default | What it does |
|---|---|---|
name | Unique in the repository. Letters, digits, _, . and -, up to 64 characters. The first character is a letter or a digit. | |
on | The triggers. At least one, and at most one manual(). | |
env | {} | Environment variables of every job. It cannot replace a variable that the forge sets: see Environment variables. |
cancelOlderRuns | true | A new run cancels the older runs of this workflow on the same pull request or branch. |
Add each job with .job(name, options). A job name has the rules of a workflow name, and is unique in the workflow.
When the plan fails
A plan fails when a workflow file does not load or breaks a rule. It also fails when a src: import cannot be used, or when it is over one of the limits above. Then no job runs, and a push or a pull request records the errors as failed runs:
- Each workflow file with errors gets a failed run with no jobs, named after the file, for at most 10 files. One run named
plangets all other errors. - The errors are the warnings of the run, at most 20 for each run.
- The runs list shows the first error. In
GET /ci/runsandlist_ci_runs,planErrorsholds the errors of the run. The run page and the checks of the pull request show them too. - A required check of the workflow stays pending. Such a run cannot run again: fix the errors, and push.
- When the plan of the hidden workflows fails, the runs are hidden runs, for admins only.
A manual start (Run workflow, src ci start) is refused with the first error. On your machine, src ci plan shows the errors of your checkout.
Triggers
| Trigger | Options | A run starts when |
|---|---|---|
pullRequest({ branches, paths }) | branches: patterns of the base branch. paths: patterns of the changed files. | A pull request opens, and at each push to its head branch. |
push({ branches, tags, paths, skipCiPushes }) | branches, tags, paths: patterns. skipCiPushes (default false): ignore the pushes that a CI job made. | A push moves a branch or a tag that matches. Without branches and tags, every push matches. With only one of them, only that kind of ref matches. |
schedule(cron) | Five fields, in UTC: minute, hour, day of the month, month, day of the week. | The time of the schedule comes. |
manual({ inputs }) | inputs: see Manual inputs. | A person with write access starts it: Run workflow on the CI tab, src ci start, REST or MCP. |
after(workflow, { conclusion }) | conclusion: 'success' (default), 'failure' or 'any'. | A run of the other workflow ends with that conclusion. |
Patterns:
*matches any characters except/.**matches any characters.?matches one character that is not/.- A leading
!removes the values that it matches. The last pattern that matches a value decides. - A list needs at least one pattern without
!. A pattern has at most 256 characters. - A
pathsfilter matches when one changed file matches. A push of a tag ignorespaths. - A change of more than 1,000 files matches every
pathsfilter.
Rules of schedule() and after():
schedule(): only the plan of the default branch counts. The plan refuses a schedule that runs more often than every 5 minutes. A schedule that missed several times runs once. When the plan of the default branch fails, the repository has no schedules until a push fixes the plan.after(): the name must be another workflow of the repository. The plan refuses an unknown name.- An
after()run uses the same commit and ref as the run that ended. - A cancelled run starts no
after()run. A run of a pull request from a fork starts none either. - A workflow runs at most once in one chain of
after()runs.
Manual inputs
| Field | Input types | What it does |
|---|---|---|
type | all | 'string', 'boolean' or 'choice'. |
description | all | The text of the input in the form. |
required | all | A manual run must give a value. |
default | all | The value when a run gives none. For 'choice', one of the options. |
options | 'choice' | The values to pick from. At least one. |
The steps get the values in ctx.inputs, with their types. An input with a default always has a value: runs of other events get the default.
Trust levels
The forge sets the level of each run. A workflow file cannot change it. Trust levels and secrets explains each rule.
| Event | Level |
|---|---|
| Pull request | trusted when the author has write access to the base repository, else untrusted. |
| Push | protected on a protected ref, when the person has write access. trusted on any other branch. untrusted without write access. |
manual() | protected on a protected ref, else trusted. It needs write access. |
schedule() | protected. |
after() | The level of the run that ended. |
A protected ref is the default branch, a tag, or a branch with a branch rule. A run that a CI push starts gets the lower of two levels: the level of the run that pushed, and the level of the target. A local run (src ci run) has the level local.
Job options
What runs, and where:
| Option | Default | What it does |
|---|---|---|
needs | [] | Jobs declared before this one. They end first. |
when | 'success' | 'success': runs when all its needs passed, else is skipped. 'always': runs when all its needs ended, with any result. 'on-failure': runs only when one of its needs failed. |
if | none | (ctx) => boolean. It runs in the job's container before the first step, with the ctx of the steps: the event, the ref, the inputs and the outputs of its needs. When it returns false, no step runs and the job is skipped. Its check run then shows success, with "skipped" in its title. |
image | 'node' | 'node' or 'browser'. See Images and sizes. |
size | 'large' | 'small', 'medium' or 'large'. |
timeoutMinutes | 60 | A whole number from 1 to 120. |
checkout | { depth: 1, lfs: false } | depth: 0 checks out all the history. lfs: true checks out the LFS files. |
matrix | none | Runs the job once for each combination of the values. At most 256 cells. |
warm | none | { files, through }. Later jobs with the same files mount the checkout and the home folder of this job, as they were after the step through. See Warm starts. |
What the job can reach, and what it gives:
| Option | Default | What it does |
|---|---|---|
secrets | {} | Secret names. {} is a required secret: with no value at the trust level of the run, the job is skipped. { optional: true } gives undefined there. |
vars | [] | Names of CI variables, at most 100. The job gets each one that the repository has as an environment variable. |
egress | [] | Exact host names that the job reaches, on port 443. No other host. |
permissions | none | What the run token can write. See Permissions. |
outputs | [] | Names that the steps set with ctx.setOutput. The jobs that need this job read them. |
env | {} | Environment variables of the job, on top of the env of the workflow. It cannot replace a variable that the forge sets either. |
steps | At least one step. |
Steps
| Kind | Fields | How it runs |
|---|---|---|
| Command | name, run, env, workingDirectory, if | bash --noprofile --norc -eo pipefail, in the workspace or in workingDirectory. |
| Function | name, do, if | The runner awaits do(ctx). The step fails when it throws or rejects. |
if(ctx)returnstrueorfalse, or a promise of one. Withfalse, the step is skipped. Any other value fails the step.- When a step fails, the job fails, and the steps after it are skipped.
- After the last step, the functions that steps gave to
ctx.afterJobrun, the last one first. When the job failed, only those withalways: truerun.
ctx
A function step gets ctx, typed from the job and the workflow.
Values:
| Member | What it holds |
|---|---|
event | The trigger type: 'pull_request', 'push', 'schedule', 'manual' or 'after'. |
trust | 'untrusted', 'trusted', 'protected' or 'local'. |
matrix | The values of the matrix cell of this job, with their exact types. |
inputs | The values of the manual inputs. |
needs | For each needed job, its result, and its outputs. For a matrix job, result and cells, each with matrix, result and outputs. |
secrets | A placeholder for each declared secret. The forge swaps it for the value only in requests to the hosts of the secret. |
vars | The value of each CI variable that the job declares, or undefined when the repository does not have it. |
env | The environment of the job. |
base, head, ref | The commit to compare with, the commit that the run tests, and the full ref of the run. |
workspace | The path of the checked-out repository. |
What base is, by event:
- Pull request: the head commit of the base branch when the run starts.
- Push: the head of the last run of the same workflow that passed on that ref. If the workflow never passed there, the commit of the ref before the push. A new branch compares with the head of the default branch.
- Push of a new tag, first push of the default branch,
schedule()andmanual(): empty. after(): thebaseof the run that ended.
Functions:
| Member | What it does |
|---|---|
sh`...` | Runs a command in bash, in the workspace. Each value in ${} is one shell word, and an array is one word for each item. A plain string runs as it is. Throws when the exit code is not 0. |
sh.nothrow`...` | The same, but returns { code, stdout, stderr } for any exit code. |
setOutput(name, value) | Sets a declared output. All outputs of a job take at most 1 MiB. |
summary(markdown) | Adds markdown to the job summary, at most 1 MiB. |
afterJob(fn, { name, always }) | Runs fn after the last step, as a step with its own log. With always: true, it runs also when the job failed. |
addPath(dir) | Puts a folder first on the PATH of the steps after this one. |
Environment variables
Each job also gets these environment variables:
| Variable | Value |
|---|---|
CI | true |
FORCE_COLOR | 1, so tools print colors to the log, and the run page shows them. Set it to 0 in env to turn colors off. |
TSS_TOKEN | The placeholder of the run token. The forge swaps it only for its own git host and REST API. |
TSS_FORGE_URL, TSS_REPO | The REST base for forge(), and the repository as owner/name. |
NX_HEAD, NX_BASE | ctx.head, and ctx.base when it is not empty. |
TANSTACK_SOURCE_JOB_ID, TANSTACK_SOURCE_SHA, TANSTACK_SOURCE_REF, TANSTACK_SOURCE_REPOSITORY | The job, the commit, the ref and the repository. Each one also has a GITFLARE_ name. |
The env of a workflow or a job cannot replace a variable that the forge sets: CI, NX_BASE, NX_HEAD, and every TSS_*, TANSTACK_SOURCE_* and GITFLARE_* variable.
- The job keeps the value of the forge, and runs.
- The log of the first step starts with a warning that names the variables that the
envtried to set. - The
envof a command step can still set them, for that step only.
Built-in steps
Import them from @tanstack-source/ci. Each one returns a function step. Use it in steps, or call its do(ctx) in your own step.
Setup:
| Step | What it does |
|---|---|
setup.node({ version }) | Puts the Node of version, else of .nvmrc or .node-version, first on the PATH. When the Node on the PATH is another version, it downloads Node from nodejs.org. A version can be 24, 24.3, lts/*, lts/<name> or node. |
setup.pnpm() | Puts the pnpm that packageManager in package.json names (like pnpm@10.4.1) first on the PATH, through corepack. Fails when packageManager names no pnpm. |
setup.bun({ version }) | Puts the bun of version, else of .bun-version, first on the PATH. When the bun on the PATH is another version, it downloads that exact version from the GitHub release of bun. |
Files and results:
| Step | What it does |
|---|---|
cache({ key, paths, restoreKeys }) | Restores key, else the newest entry of the first prefix in restoreKeys that has one. When the job passes and no entry had exactly key, it saves paths as key after the last step. |
artifact({ name, paths, retentionDays }) | Uploads the files as one tar.gz artifact of the run. With no matching file, it uploads nothing and passes. |
fixup({ message }) | Sends what the steps before it changed, compared with the tested commit. The forge commits it to the head branch of the pull request, or shows it as a suggested patch. |
report({ file, text, format }) | Reads the output of a tool: a file of the workspace, or text. Adds its errors and warnings to the findings of the run. |
The rules of each:
cache(): a key has 1 to 512 printable ASCII characters. At most 10 restore keys. A path is relative to the workspace, under~, or a glob, with no!. An entry is written once. A restore or save that fails is logged and does not fail the job. The cache scopes depend on the trust level. In a job container, an entry is atar.zstthat the step packs on every core, and a restore reads a large entry in several parts at once, straight intotar. In a local run, an entry is atar.gz.artifact(): a name has 1 to 200 characters, with no/,\or control character. A path outside the workspace fails the step.retentionDaysis a whole number from 1, and can only make the time shorter.fixup(): the step fails when a change is in.source/, when the fixup is over 5 MiB, or when a submodule changed. The job needs no push permission. Pull requests from a fork lists when the forge commits it.report(): as a step, it runs only when the steps before it passed. To report a tool that failed, callawait report({ text, format }).do(ctx)in your step before the step fails.
report() formats. Without format, the step detects it.
format | Reads |
|---|---|
vitest | The JSON reporter of vitest (vitest run --reporter=json). |
jest | The JSON output of jest (jest --json). |
junit | JUnit XML, from vitest, jest-junit, node --test, pytest and other test runners. |
tsc | The output of tsc, with --pretty false or without it. |
eslint | The JSON output of eslint (eslint -f json). |
playwright | The JSON reporter of Playwright (playwright test --reporter=json). |
log | The ::error, ::warning and ::notice lines of a log. |
One report keeps at most 1,000 findings, and a message keeps at most 4,000 characters.
forge(ctx)
A client of the REST API of the forge, for the repository of the run. It reads TSS_FORGE_URL, TSS_REPO and TSS_TOKEN from ctx.env.
| Method | What it does | Permission |
|---|---|---|
pulls.list({ head, state, label }) | Lists the pull requests, every page. head is owner:branch for a fork. | none |
pulls.create({ title, head, base, body }) | Opens a pull request. | pulls: 'create' |
comments.create(number, body) | Comments on an issue or a pull request. | comment |
issues.list({ label, state }) | Lists the issues, every page. | none |
issues.create({ title, body, labels }) | Opens an issue, then adds the labels. | issues: 'write' |
labels.ensure({ name, color, description }) | Gives the label with that name, or creates it. | issues: 'write' to create |
labels.add(number, names) | Adds labels to an issue or a pull request, and creates the missing ones. | issues: 'write' |
- A refused request rejects with a
ForgeError, with the HTTPstatusand the message of the forge. - A read is tried again after a network error, a 5xx or a 429 (after 0.25 s, 1 s and 4 s). A write is sent once.
- Without
TSS_FORGE_URL, each write is a dry run: the step prints it, and sends nothing. Reads find nothing.
Warm starts
warm: { files, through } makes later jobs mount the checkout and the home folder of an earlier job, as they were when its step through passed. Start jobs warm shows it step by step.
warm: { files: ['pnpm-lock.yaml', 'package.json', '.nvmrc'], through: 'Install dependencies' }
The rules:
files: 1 to 20 paths in the repository. A path has no empty,.or..folder, and does not start with/. A path that is not in the commit is part of the key too.through: the name of a step of the job. With two steps of that name, the first one.- The key:
warm-and a hash of the CI image (nodeorbrowser), the workflow file, the job, its matrix cell, and the git id of each offilesat the commit that the run tests. For a pull request from a fork, the commit is in the fork. No code of the repository runs for the key. - The image is the cache entry of the key. It has the cache scopes, counts in the cache size of the repository, and goes when the cache evicts it. An untrusted run without a pull request saves none.
- A hidden workflow takes no warm start and makes none.
- In a local run (
src ci run),warmdoes nothing: every step runs.
What an image holds:
- The checkout and the home folder (
~) when the stepthroughpassed, packed bymksquashfswith zstd. Files in other folders, like the packages ofapt-get install, are not in it. - The commit that the job tested, and the folders that the steps put on the
PATHwithctx.addPath, like the Node ofsetup.node().
A job whose key has no image:
- Runs every step. When
throughpasses, it packs the image. A step at the end of the job,Save the warm start, saves it, also when a later step failed.
A job whose key has an image:
- Before its checkout, downloads the image and mounts it read-only, with an overlay that takes the writes of the job, at the checkout and at the home folder. Nothing is extracted.
- Moves the checkout of the image to the commit of its run. The files that the
.gitignoreof the commit ignores stay, likenode_modules. Every other file of the earlier checkout goes. - Does not run the steps up to and including
through, or thectx.afterJobfunctions that those steps register. So acache()step beforethroughsaves nothing. - Runs the steps after
throughas usual.
What the log and the step list show:
| When | What you see |
|---|---|
| The key has no image | The first line of the log: No warm start for these files yet: this job installs, and saves one for the next jobs. A run with no write scope says ...and its run saves none. |
| The job packed the image | The last line of the log of through: Packed the warm start: an image of <size> MiB. The job saves it at its end. Then the step Save the warm start says Saved the cache warm-<hash>. |
| The job could not pack the image | That line says why, like No warm start was packed: .... The job goes on. |
| The job mounted an image | Each skipped step has (warm start) after its name, a time of 0 s, and one log line: Taken from the warm start that a job of commit <commit> packed. This step did not run. |
| The image did not mount | The first line of the log: The warm start did not mount (<why>): this job installs. |
A warm start never makes a job fail. When the image does not mount, the job runs every step.
Permissions
A job declares permissions. The default is none: the run token only reads. The trust level of the run sets a ceiling.
| Permission | What it allows | untrusted | trusted | protected |
|---|---|---|---|---|
| none | Read the repository. | yes | yes | yes |
comment: 'own-pr' | Comment on the pull request of the run. | no | yes | yes |
comment: 'any-pr' | Comment on any pull request. | no | gets 'own-pr' | yes |
push: 'own-pr' | Push to the head branch of the pull request of the run. | no | yes | yes |
push: ['<pattern>', ...] | Push to the branches that match. 'tags' means all tags. | no | yes, but not to a protected ref | yes |
issues: 'write' | Create and edit issues, and change their labels. | no | no | yes |
pulls: 'create' | Open pull requests. | no | no | yes |
The run shows a warning for each permission that its level takes away. The forge audits each write with the run, the job and the person who started the run.
Images and sizes
| Image | What it has |
|---|---|
node | Debian 13, Node 24, corepack (pnpm, yarn), bun, git, git-lfs, jq, python3, build-essential, curl, unzip, xz-utils and zstd. |
browser | The node image, plus Playwright 1.57.0 with Chromium, Firefox and WebKit in /ms-playwright. |
The images are for linux/amd64, and the steps run as root.
In both images, fetch in Node 22.19 and later goes through undici 8. The fetch of Node 24 (undici 7) can stop the whole process with assert(!this.paused) when a server closes the connection during a slow download, as when corepack unpacks pnpm. A step whose env sets NODE_OPTIONS keeps the fetch of its Node.
In both images, npm, pnpm and bun reach the npm registry over plain http inside the container, 64 requests at a time. /root/.npmrc and /root/.bunfig.toml set this. The job's egress still fetches from the registry over https. A project's own .npmrc or bunfig.toml wins over these files. A job opens a new connection for each request, so a large install with https spends much of its time on TLS handshakes. With http and 64 requests at a time, a cold install of TanStack/ai took 68 s, against 140 s.
| Size | vCPU | Memory | Disk |
|---|---|---|---|
small | 1 | 6 GiB | 12 GB |
medium | 2 | 8 GiB | 16 GB |
large | 4 | 12 GiB | 20 GB |
A job is large by default: 4 vCPU, as a GitHub runner has. A package install links thousands of files, and more vCPU makes it faster. Give a job that only runs a short script size: 'small' or size: 'medium'.
Where jobs run
The CI jobs of a repository run near the repository's data, which is near the place where the repository was made. So a team in Europe gets jobs in Europe.
Each job reads its checkout and its cache from storage in the repository's region:
- Eastern Europe or Western Europe;
- western or eastern North America;
- Asia-Pacific or Oceania.
At the first CI run of a repository, the forge measures which region is nearest to the repository's data, and keeps that region.
Limits
Jobs:
| Limit | Value |
|---|---|
| Matrix cells of a job | 256 |
timeoutMinutes | 1 to 120. The default is 60. |
| Log of a job | 128 MiB over all its steps. The forge keeps no log bytes past it. |
| Outputs, and the summary, of a job | 1 MiB each |
| Outbound requests of a job | 50,000. A request past it gets 429, and the job error says why. The forge audits the first 500 requests one by one, and counts the rest by host. See The egress audit. |
| CI pushes in a row | 3. A CI push past that starts no run. |
When the platform stops a job's container while the job runs, the job ends with the error "The job lost its container", and the forge runs its cell once more, in a new job. When that job loses its container too, the cell ends as an error.
Storage:
| Limit | Value |
|---|---|
| Artifact | 1 GiB each, and 5 GiB for each run |
| Artifact retention | 30 days, and 90 days for a protected run |
| Cache of a repository | 10 GB. The least recently used entries go first. |
| Cache entry | 5 GiB less 5 MiB |
fixup() | 5 MiB |
The queue: each account has a queue of its own, and its repositories take turns. A job past a limit waits in the queue. It does not fail. The jobs of software factory runs wait in the same queue, and these limits count them too. These are the defaults, and the operator of the forge can change them. No limit covers the whole forge.
| Containers at the same time | Default |
|---|---|
| For one account (the owner of the repositories) | 20 |
| For one repository | 20. Lower it with src ci settings --containers <n>. |
| For the untrusted jobs of one fork author, in all accounts | 4, and 4 hours of container time each UTC day. The factory jobs on the items of an author without write access count here too. |
Settings:
| Limit | Value |
|---|---|
| CI secrets | 100 for each repository, 16 KiB for each value, 10 hosts for each secret |
| CI variables | 100 for each repository, 48 KiB for each value, 256 KiB for the variables of one job |
| Branch rules | 100 for each repository, 50 required checks for each rule |
| Approval gates | 50 for each repository |
| Allowed workflow sources | 100 for each repository |
src: modules in one plan | 1 MiB of files, 100 files, 8 imports deep |
gh commands for CI
src has gh's run, workflow, cache, secret, variable and agent-task commands. They use your src login and the repository of your clone. For another repository, add -R <owner>/<repo>.
Runs:
| Command | What it does |
|---|---|
src run list [-w <workflow>] [-b <branch>] [-s <status>] [-e <event>] [-u <user>] [-c <commit>] [-L <n>] [--all] | Lists the runs, 20 by default. A status is one of gh's, like in_progress or failure. With --all, -w can name a disabled workflow. |
src run view <run> [-j <job>] [--log | --log-failed] [-v] [--exit-status] | Shows a run and its jobs, one job, or the logs. -j takes the job key, like test (shard: 1), or the job id. |
src run watch <run> [-i <seconds>] [--compact] [--exit-status] | Shows the run again every 3 seconds, until it ends. |
src run rerun <run> [--failed], src run cancel <run> | Runs a run again, or only its jobs that did not pass, with the jobs that need them. Or cancels a run. |
src run download <run> [-D <dir>] [-n <name>]... [-p <pattern>]... | Saves the artifacts of a run. Each artifact is one file. src never replaces a file. |
src run delete <run> | Deletes a run that ended. See Delete a run. |
Workflows, cache, secrets, variables and agent tasks:
| Command | What it does |
|---|---|
src workflow list [--all], src workflow view <workflow> [--yaml] [-r <ref>] | Lists the workflows of the default branch, or shows one with its recent runs. --yaml prints the workflow file. |
src workflow run <workflow> [-r <ref>] [-f <name>=<text>]... [-F <name>=<value>]... [--json] | Starts a workflow that has a manual() trigger. --json reads the inputs from stdin, as a JSON object. |
src workflow enable <workflow>, src workflow disable <workflow> | Turns a workflow on or off. See Turn a workflow off. |
src cache list [-k <prefix>] [-r <ref>] [-S <field>] [-O asc|desc] [-L <n>] | Lists the cache entries, 30 by default. |
src cache delete <key> [-r <ref>], src cache delete --all [-r <ref>] | Deletes the entry of a key in every scope, or only in the scope of -r. --all deletes every entry. |
src secret list, src secret set <name>, src secret delete <name> | See Set a secret from the terminal. |
src variable list, src variable get <name>, src variable set <name>, src variable delete <name> | See CI variables. People with write access list and get the variables. Admins set and delete them. |
src agent-task create <task> -a <agent>, src agent-task list [-L <n>], src agent-task view <id> | Gives a task to an agent, lists the agent jobs of the repository (newest first), or shows one. list takes no -R: it uses the repository of your clone. |
- A workflow is its file name, like
nightly.ts, or its name. - A
-rof the cache commands isrefs/heads/<branch>,refs/pull/<n>/mergeorprotected. --json,--jqand--templatework withrun list,run view,workflow list,cache list,secret list,variable listandvariable get. See Use src in scripts and agents.
Turn a workflow off
src workflow disable nightly.ts stops a workflow from starting runs. src workflow enable nightly.ts turns it on again.
- A disabled workflow starts no run: not from its triggers, and not by hand. A manual start is refused.
- It still plans, so a required check of it stays pending.
- Only an admin of the repository turns a workflow on or off. On the web, admins use the switch of the workflow under Workflows on the CI settings page.
src workflow listshows a disabled workflow only with--all.
Delete a run
src run delete <run> deletes a run with its logs, its artifacts and the records of its jobs.
- Only an admin of the repository deletes a run.
- The run must have ended. If it has not ended, cancel it first with
src run cancel <run>. - A run of a pull request cannot be deleted, because the required checks of the pull request read it. A run of a failed plan cannot be deleted either.
- The run goes at once. Its jobs, logs and artifacts go in the background after that.
- The check results of the run on its commit stay.
src ci commands
The src binary is in the npm package @tanstack-source/cli. src ci help shows the same list.
The commands that act on the forge use your src login and the repository of your clone, like the other src commands:
- To use another repository of the same forge, set
TSS_REPO=<owner>/<repo>. - To use another token, set
TSS_TOKEN. It wins over the token of your login. - To use another forge, set
TSS_FORGE_URL(the REST base, likehttps://forge.example/api/v1),TSS_TOKENandTSS_REPO. WhenTSS_FORGE_URLis set,src ciuses only these three variables. src ci run --livegives its steps only aTSS_TOKENthat you set, never the token of your login.
On your machine. plan, run and convert work without the forge. sync, update and --repro act on the forge:
| Command | What it does |
|---|---|
src ci plan [--json] | Plans the workflows, and shows their jobs or the errors. --json prints the plan as JSON. |
src ci run <workflow> <job> | Runs one job. Flags: --matrix <key>=<value>, --input <name>=<value>, --with-needs, --docker, --print (with --docker), --live. See Run CI jobs on your machine. |
src ci run --repro <run>/<job> | Runs a job of a forge run again, with its matrix cell, inputs and needs. |
src ci convert [<file>...] [--force] | Converts GitHub Actions workflows into .source/workflows/*.ts. See Move from GitHub Actions. |
src ci sync | Downloads the src: modules that src.lock pins into .source/.modules/. |
src ci update [<owner>/<repo>@<ref>...] | Pins src: imports to the commits of their refs now, shows what changed, and syncs. |
Runs and jobs. These, and every command below, act on the forge:
| Command | What it does |
|---|---|
src ci runs [--workflow <name>] [--pr <n>] [--branch <name>] [--status <status>] [--limit <n>] [--cursor <next>] | Lists the runs, the newest first. A status is planning, waiting_approval, queued, running, completed or cancelled. |
src ci view <run> [--job <job>] [--log | --log-failed] | Shows a run and its jobs, one job, or the logs of its jobs. |
src ci logs <run> <job> [--step <n>] [--from <line>] [--to <line>] | Prints the log of a job, or some of its lines. |
src ci findings <run> <job> | Shows the errors and warnings that a job found. |
src ci rerun <run> [--failed] | Runs a run again, or only the jobs that did not pass. |
src ci cancel <run>, src ci approve <run> | Cancels a run, or approves a run that waits for a person. |
src ci start <workflow> [--ref <ref>] [-f <name>=<text>]... [-F <name>=<true|false>]... | Starts a workflow that has a manual() trigger, with its inputs. |
Artifacts, pull requests and agent jobs:
| Command | What it does |
|---|---|
src ci artifacts <run> <job> | Lists the artifacts of a job. |
src ci download <run> <job> [--name <name>]... [--dir <dir>] | Downloads the artifacts of a job into a folder. The default is the current folder. |
src ci artifact <id> [--dir <dir>] | Downloads one artifact by its id. |
src ci checks <pr> | Shows the required checks of a pull request, and whether it can merge. |
src ci check-runs [--sha <commit>] | Lists the check runs of a commit. The default is the commit that you checked out. |
src ci fixups <pr>, src ci fixup apply <id> | Lists the fixups of a pull request, or applies a suggested one. |
src ci maintainer-edits <pr> [--on | --off] | Shows or sets whether maintainers and CI fixups can change the pull request. |
src ci job start --agent <name> --prompt <text> [--ref <ref>] [--pr <n>] [--egress <host>]..., src ci job view <id> | Starts an agent job, or shows a job. |
Settings of the repository:
| Command | What it does |
|---|---|
src ci secret list, src ci secret delete <name> | Lists the CI secrets (levels and hosts, never values), or deletes one. |
src ci secret set <name> [--untrusted <value>] [--trusted <value>] [--protected <value>] [--unset <level>]... [--host <host>]... | Sets the values and the hosts of a secret. A value of - comes from stdin. |
src ci rule list, src ci rule delete <pattern> | Lists the branch rules, or deletes one. |
src ci rule set <pattern> [--require-pull] [--check <workflow>/<job>]... [--up-to-date] [--skipped-passes] | Sets the branch rule of a pattern. A flag that you leave out is off. |
src ci source list, src ci source add <owner>/<name>, src ci source remove <owner>/<name> | Lists, adds or removes the allowed workflow sources. |
src ci cache list, src ci cache delete --scope <scope> | --key <key> | --all | Lists the cache entries, or deletes them. A scope is protected, branch:<name> or pr:<n>. |
src ci settings [--containers <n> | --clear-containers] [--gate <job>]... [--no-gates] | Shows the CI settings, or sets the container limit and the approval gates. A gate is <job> or <workflow>/<job>. |
--jsonprints the answer of the forge as it is. It works withruns,view,findings,artifacts,job view,check-runs,checks,fixups,maintainer-editsand eachlistcommand.- Short flags:
-w(--workflow),-b(--branch),-s(--status),-L(--limit),-j(--job),-r(--ref),-n(--name),-D(--dir). - The exit code is 0 when the command did its work, and 1 when it failed. It is 2 when the command line is wrong. For the commands of the forge, it is 2 also when
srcfinds no login or no repository, and aTSS_*variable is missing.
REST and MCP
The REST routes are under /api/v1/repos/<owner>/<repo>. Each MCP tool calls the same method as its route. A job key has its matrix cell, like test (shard: 1).
Runs and jobs:
| REST | MCP tool |
|---|---|
GET /ci/runs?workflow=&pull=&ref=&status=&limit=&cursor= | list_ci_runs |
GET /ci/runs/<run> | get_ci_run |
POST /ci/runs/<run>/approve, /cancel, /rerun ({ "failedOnly": true }) | approve_ci_run, cancel_ci_run, rerun_ci_run |
DELETE /ci/runs/<run> | delete_ci_run |
GET /ci/workflows, GET /ci/workflows/<workflow> | list_ci_workflows, get_ci_workflow |
PUT or DELETE /ci/workflows/<workflow>/enabled | set_ci_workflow_enabled |
POST /ci/workflows/<workflow>/runs ({ "ref", "inputs" }) | start_ci_workflow |
GET /ci/runs/<run>/jobs/<job>/logs?step=&from=&to= (1,000 lines by default) | read_ci_job_log |
GET /ci/runs/<run>/jobs/<job>/findings | get_ci_job_findings |
GET /ci/runs/<run>/jobs/<job>/artifacts | list_ci_job_artifacts |
GET /ci/runs/<run>/jobs/<job>/repro | get_ci_job_repro |
GET /artifacts/<id> (with Range) | get_artifact |
The X-Log-Steps header of the logs route, and steps in read_ci_job_log, give each step of the log:
step(from 0),name, andlines.finished: the log of the step ended.conclusion(success,failureorskipped) anddurationMs: null until the step ends.
A step skipped because an earlier step failed has no lines. name is null for a step that the runner did not report. The header escapes each character that is not ASCII, like é.
The run lists read an index of the runs, which can lag behind a run by a few seconds:
GET /ci/runs,list_ci_runs,src ci runsand the CI tab show a new run, or a change of a run, after those seconds.- Until the index has a new run, the routes of that run answer
404. This includes a run thatPOST /ci/workflows/<workflow>/runsstarted. - When the index has a run,
GET /ci/runs/<run>gives its current state.
Pull requests, check runs and agent jobs:
| REST | MCP tool |
|---|---|
GET /pulls/<n>/checks | get_pull_request_checks |
GET or PUT /pulls/<n>/maintainer-edits ({ "allowed" }) | get_maintainer_edits, set_maintainer_edits |
GET /pulls/<n>/fixups | list_pull_request_fixups |
POST /ci/fixups/<id>/apply | apply_ci_fixup |
GET /check-runs?sha= | list_check_runs |
POST /jobs, GET /jobs/<id> | start_job, get_job |
GET /jobs?kind=agent&limit=&cursor= | list_agent_jobs |
Settings:
| REST | MCP tool |
|---|---|
GET /ci/secrets, PUT or DELETE /ci/secrets/<name> | list_ci_secrets, set_ci_secret, delete_ci_secret |
GET /ci/variables, GET, PUT or DELETE /ci/variables/<name> ({ "value" }) | list_ci_variables, get_ci_variable, set_ci_variable, delete_ci_variable |
GET /ci/rules, PUT or DELETE /ci/rules/<pattern> | list_branch_rules, set_branch_rule, delete_branch_rule |
GET or POST /ci/sources ({ "source": "owner/name" }), DELETE /ci/sources/<owner>/<name> | list_ci_sources, add_ci_source, delete_ci_source |
GET /ci/cache, DELETE /ci/cache?scope=&key= | list_ci_cache, delete_ci_cache |
GET or PUT /ci/settings ({ "containers", "approvalGates" }) | get_ci_settings, set_ci_settings |
Who can:
- Everyone who can read the runs lists the workflows and the agent jobs.
- People with write access read the CI variables. Admins set and delete them.
- Admins turn a workflow on or off, and delete a run. A run of a pull request, or a run that has not ended, gets
422.
Two MCP prompts help agents: fix_ci_run fixes a failed run, and convert_github_actions moves workflows from GitHub Actions.