Checks live
What must a bean pass to land? One file, .gitstalk/checks.yml, answers it.
It is read from the exact tree each check runs on, so a change to the checks is checked
by its own copy. Like the approval rules and automations, it is YAML: all
.gitstalk/ configuration is YAML now.
See which suite ran
Every push says which suite its check used. With no file, the default runs:
node --test with a 300-second limit.
$ git push -o wait origin HEAD:refs/heads/bean/add-truncate remote: gitstalk: new bean add-truncate received at 3f9a1c2: "Add truncate(text, n)" remote: gitstalk: pre-land check started: your change is merged onto the sprout and the whole suite runs on that tree remote: gitstalk: no .gitstalk/checks.yml on this tree: the repository's default suite runs: node --test (timeout 300 s) remote: gitstalk: LANDED: add-truncate passed its pre-land check and is on the sprout as 8d41e07
checks from .gitstalk/checks.yml and
names your command, image, time limit and any environment names.
The file
# yaml-language-server: $schema=https://gitstalk.dev/schemas/checks.json
image: node
timeout-minutes: 1
env:
KV_LIMIT: "3"
run: [node, --test, "spec/**/*.spec.mjs"]
protected-paths:
- data/schema.json
- migrations/**
Keys are named the way GitHub Actions names the same things
(timeout-minutes,
env, kebab-case). The first line gives editors that use the YAML language
server completion and checking from the file's JSON Schema.
| Key | Default | Rules |
|---|---|---|
image |
node |
Only node today (Node 25). Other languages need their own runner
image; not built yet.
|
run |
[node, --test] |
A list of arguments, never a shell (so not npm test). Must start
with node and include --test; the rest are options and
test globs. Quote a glob that starts with *.
|
timeout-minutes |
5 |
Up to 30, and it must come to whole seconds (0.5 is 30 s). A suite
that runs out of time is run again; a pre-land check is never reported red for
timing out.
|
timeout-seconds |
300 |
1 to 1,800, instead of timeout-minutes (write one, not both).
|
protected-paths |
[] |
Patterns: * within a segment, ** across segments, a
trailing / for a directory. Relative to the repository root. Every
checks file and .gitstalk/policy.yml are always protected.
|
env |
none |
Up to 32 upper-case names with string values: quote numbers and
true/false ("10"). The runner's own
(PATH, HOME, CI, GIT_*,
LD_*, NODE_TEST*, BWRAP*) are refused.
Never put a secret here: the file is in your repository. See
Environment variables.
|
Why run is a list
In a GitHub Actions workflow, run: is a string that a shell runs. Checks
never use a shell, so a string would either mean something different here (no pipes, no
&&, no shell quoting) or need a splitting rule of its own. A list
says exactly what the runner starts, so run: npm test is refused with the
reason, and so is a bare number in the list.
Parsing is strict, as for the approval rules: YAML 1.2 (no stays a string),
no duplicate keys, no anchors, aliases or merge keys. Every problem names its line,
column and key, and unknown keys are refused by name, with a guess when one is close:
.gitstalk/checks.yml is invalid:
line 2, column 1: unknown key "rnu" (did you mean "run"?)
| The tree has | The check |
|---|---|
| no file | The default suite, and the push says so. |
| a valid file | Its command, environment and time limit. |
| an invalid file | Red without running anything, naming every problem with its line, column and key. |
Settings, Checks
Settings, Checks shows what the next check will run. It reads the file from the stalk with the same parser the engine uses, so the page and the check cannot disagree. It is read-only: the form never edits the file. Everyone with a role can see it, with the file itself as YAML.
A repository whose checks are still in checks.toml sees the equivalent YAML
and, for the owner and maintainers, a Convert to YAML button. It saves
one bean, authored by you, that writes .gitstalk/checks.yml and deletes the
TOML file in the same commit; it lands through the pre-land check like any change.
Protected paths
A bean that changes a protected path is red before any test runs, unless it was pushed by the owner or a maintainer with their own personal token or SSH key. This is how an agent is kept from weakening the checks it is judged by.
$ git push -o wait origin HEAD:refs/heads/bean/schema remote: gitstalk: changes protected paths (data/schema.json): refused for a deploy token acting for @coop remote: gitstalk: RED: schema was not landed. Merged onto the sprout, these tests failed: remote: gitstalk: - data/schema.json remote: gitstalk: ...
allowed for instead.
| Who pushes | May change protected paths? |
|---|---|
| Owner or maintainer, with a personal token or an SSH key | Yes |
| Writer or reader | No |
| Agent session (MCP), whoever it acts for | No |
| Deploy token | No |
The patterns come from the sprout's copy of the file, so a bean cannot unprotect itself. For rules beyond paths, such as letting comment-only changes land on their own or sending risky areas to a person, see Approval rules.
Older files: TOML and .beanstalk/
Until 14 October 2026 the file was .gitstalk/checks.toml. It still works,
with the same capabilities and snake_case keys (command,
timeout_seconds, protected_paths, an
[env] table). A tree's checks come from the first of these it has:
.gitstalk/checks.yml.gitstalk/checks.yaml.gitstalk/checks.toml- the same three in
.beanstalk/
Every one of them is always protected. Messages always name the file that was read. A TOML file adds a push line saying YAML is the format now. If a tree has two, the first wins and the push says the other is not read:
remote: gitstalk: checks from .gitstalk/checks.yml: node --test (image node, timeout 120 s)
remote: gitstalk: .gitstalk/checks.toml is also on this tree and is not read (.gitstalk/checks.yml wins): delete it (git rm .gitstalk/checks.toml)
| checks.toml | checks.yml |
|---|---|
command = ["node", "--test"] |
run: [node, --test] |
timeout_seconds = 120 |
timeout-minutes: 2 or timeout-seconds: 120 |
protected_paths = ["migrations/**"] |
protected-paths: [migrations/**] |
[env] then TZ = "UTC" |
env: then TZ: UTC |