Gitslice docs

CI Checks

Checks are build/test/lint commands defined in your source. They run automatically on every new patchset and can gate submit. No separate CI service to configure — checks run on a machine you control and report back.

How it works

  • Defined in source: checks live in .gitslice/checks.yaml files committed alongside your code — one per folder. They are versioned with the revision they apply to.
  • Folder-scoped & cascading: for each path a patchset changes, every checks.yaml from that folder up to the repository root applies. A check defined in backend/ only runs when something under backend/ changes; an optional paths filter narrows it further.
  • Runs on each patchset: when you capture a patchset (including each agent turn), the applicable checks run and their pass/fail is recorded against that patchset. A new patchset re-runs them.
  • Gates submit: a slice can mark checks as required. The changeset cannot submit until every required check passes for the current patchset.
  • Where they run: in the common case checks run on the same machine that authored the change (your agent or CLI), in a container when an image is set, otherwise directly. Checks that need files beyond the slice run on the slice's designated CI daemon.

The checks.yaml format

Place this at <folder>/.gitslice/checks.yaml. A check's id is its folder path plus its name, e.g. backend/test.

version: 1

# optional defaults applied to every check in this file
defaults:
  image: "golang:1.22"     # run in this container; omit to run on the host
  timeout: "10m"
  setup:                   # baked once into a cached image, reused across runs
    - "apt-get update && apt-get install -y protobuf-compiler"

checks:
  test:
    run: "go test ./..."   # required: the shell command
    paths: ["**/*.go"]     # optional: only run when these change
  lint:
    run: "golangci-lint run"
  smoke:
    run: "./scripts/smoke.sh"
    include: ["/go.mod", "/go.sum"]  # extra paths to make available
    network: true                    # allow network (default: denied)
  • run (required) — the command, run via sh -c from the check's folder. Exit 0 passes.
  • paths — globs (folder-relative, or /-absolute); the check is skipped if none match the change.
  • image, env, network, timeout, working_dir, include — all optional per check (or under defaults).
  • setup — commands run once to prepare the container image (install toolchains/system packages). The result is baked into a cached image and reused on later runs, so setup does not repeat. Keep it change-independent (no workspace files); use a dependency cache for per-change caches like go mod or npm.

Run the applicable checks for your current workspace at any time with gs ci — it runs the same set locally (with setup/run timing) and reports pass/fail without capturing or submitting.

Build the check image from your tree

Use build instead of image when the environment belongs in the revision. They are mutually exclusive. The daemon builds the image from the tree and reuses it until the Dockerfile or an input changes.

defaults:
  build:
    dockerfile: ci/Dockerfile
    context: "."
    inputs:
      - go.mod
      - go.sum
      - web/package-lock.json
    timeout: "30m"

Only the Dockerfile and listed inputs are sent to the build. Include dependency manifests such as go.mod, go.sum, and package-lock.json there, then install dependencies in the image so later runs reuse them. To bound disk use, the daemon keeps the two newest images for each Dockerfile.

Add services for a check

Define a top-level services map, then list service names on a check. A service accepts image, env, ready, ready_timeout, and memory.

services:
  postgres:
    image: postgres:16-alpine
    env: { POSTGRES_HOST_AUTH_METHOD: trust }
    ready: "pg_isready -U postgres -h 127.0.0.1"
    ready_timeout: "60s"
    memory: "1g"

checks:
  e2e:
    build:
      dockerfile: ci/Dockerfile
      inputs: [go.mod, go.sum]
    services: [postgres]
    env:
      GITSLICE_TEST_DATABASE_URL: "postgres://postgres@postgres:5432/postgres?sslmode=disable"
    run: "go test ./tests/..."

Every run gets fresh service containers on a private network and reaches each service by its name (for example, postgres). The network is internal unless the check sets network: true. Services need a containerized check, using image or build.

Containers run as the daemon's user. A check's cache paths are folders under that daemon's cache directory, so they are writable and can be cleaned up there.

Run a CI daemon on any machine

A CI-only daemon runs checks and nothing else. It needs only gs and, when checks use image or build, Docker or Podman.

gs auth login
gs agent doctor --ci
gs agent install --ci --name my-ci
gs agent expose my-ci <account>:<slice>

# A slice owner chooses the exposed daemon.
gs slice set-ci-daemon <account>:<slice> <daemon-id>

# Remove the user service when it is no longer needed.
gs agent uninstall my-ci

When this daemon is offline, its runs remain queued. The Checks panel and gs submit say that the daemon is offline and checks begin when it reconnects.

A checks-only daemon with nothing to run detaches from the server after two minutes and polls every 30 seconds for queued checks, so an idle daemon costs the server almost nothing. It stays online, and a queued check may start up to about 30 seconds later. --detach-after 0 keeps it connected.

Pass secrets to checks

A secret is a value Gitslice keeps encrypted and gives to a check as an environment variable named after it. It belongs to an account and reaches the slices it is scoped to, or all of the account's slices. The account's owners and admins manage secrets withgs secretor on the account's Secrets page. A value is never shown again once it is set.

printf %s "$TOKEN" | gs secret create DEPLOY_TOKEN --slice api --slice worker
printf %s "$TOKEN" | gs secret create NPM_TOKEN --all-slices
gs secret ls
gs secret update <secret-id> --add-slice web
gs secret rm <secret-id>

A slice gets at most one secret of each name: a secret that lists the slice overrides an all-slices one there. Every writer of a slice can read its secrets from a check, and so can its CI daemon, so widen a scope with care.

Add checks

  1. 1

    Commit a checks.yaml

    Add .gitslice/checks.yaml at the folder you want to guard, with one or more checks. It is a normal source file in your slice.

  2. 2

    Mark the ones that must pass

    Make a check required so it gates submit. Reference it by its qualified id (folder path + name).

    gs slice update <account>:<slice> --required-check backend/test
  3. 3

    Capture and watch

    Capture a patchset (or let your agent do it). Applicable checks run and show on the changeset's Checks panel; required failures block submit until fixed.

    gs submit   # blocked until required checks pass
  4. 4

    Optional: a designated CI runner

    Checks that need files outside the slice (e.g. a repo-root check) run on a daemon you designate for the slice, in Settings or via the CLI. You can designate daemons you started, those of agents you own, and daemons whose owners exposed them to the slice.

    gs slice set-ci-daemon <account>:<slice> <daemon-id>

Understand the model

See how changesets, patchsets, and submit validation fit together.

Run an agent

Let an agent author changes — checks run on each of its turns.