GitLab pipeline context contract
loom.gitlab-pipeline-context.v1 is the caller-owned JSON document that
loom check, loom compile, and loom run read through --context (or
defaults.context) when the workflow source is GitLab. It supplies the
pipeline facts GitLab would inject on a runner so that workflow:rules,
rules:, and variable expansion select the same jobs locally.
Where it comes from
loom context init --as push|mr [--name <name>]writes.loom/contexts/<name>.jsonfrom the checked-out branch. Seeloom context.- Any hand-written file that follows this contract works the same way.
--context <name>without a path separator or.jsonsuffix resolves to.loom/contexts/<name>.json; other values are project-relative paths beneath the repository root. Files are limited to 1 MiB.
Document shape
{
"version": "loom.gitlab-pipeline-context.v1",
"variables": {
"CI_PIPELINE_SOURCE": "push",
"CI_COMMIT_BRANCH": "main",
"CI_DEFAULT_BRANCH": "main",
"CI_COMMIT_SHA": "9ec2bc2e9b997525703e29917db2a21a86ad75cc",
"CI_COMMIT_BEFORE_SHA": "119abcd8d4282468372df322f92cc51c60b30ac7"
},
"changes": {
"base": "119abcd8d4282468372df322f92cc51c60b30ac7",
"head": "9ec2bc2e9b997525703e29917db2a21a86ad75cc"
},
"runner_tags": ["saas-linux-medium-amd64"]
}
Only the four top-level keys below are accepted; any other key fails decoding
with GL_CONTEXT_TYPE_INVALID and the accepted key list.
| Key | Required | Type | Meaning |
|---|---|---|---|
version | yes | string | Exactly loom.gitlab-pipeline-context.v1 |
variables | yes | object of strings | Pipeline variables visible to rules and expansion (may be empty) |
changes | no | object | The comparison rules:changes evaluate against |
runner_tags | no | array of strings | Tags the local runner claims to satisfy; not job-selection data |
variables
Every value is a string. Names follow GitLab's predefined variables
(CI_PIPELINE_SOURCE, CI_COMMIT_BRANCH, CI_DEFAULT_BRANCH,
CI_COMMIT_SHA, CI_COMMIT_BEFORE_SHA, CI_COMMIT_REF_SLUG,
CI_MERGE_REQUEST_*, ...). Undefined variables evaluate as null in rules.
Explicit directory values (CI_BUILDS_DIR, CI_PROJECT_DIR) are ordinary
caller overrides available to earlier consumers. When absent, their actual
defaults remain unavailable before provider assignment; earlier-phase use fails
with GL_CONTEXT_PHASE_UNAVAILABLE.
Ambient environment variables of the loom process are never consulted.
changes
| Field | Required | Meaning |
|---|---|---|
base | yes | Commit, branch, or ref the comparison starts from (merge_base(base, head)..head) |
head | yes | Commit or ref being compared; HEAD compares the working tree |
paths | no | Explicit changed paths; when present, git is never consulted and these paths always win |
Without paths, Loom resolves the changed paths with
git diff --merge-base in the repository root, using the rule's compare_to
as the base when the rule sets one. Without a changes object, a rule with
rules:changes cannot be evaluated unless it carries compare_to, in which
case the working tree is compared. Unknown refs and shallow clones without a
merge base fail closed with GL_RULE_CHANGES_UNAVAILABLE.
runner_tags
Trimmed, non-empty, unique strings. A selected job whose tags are not all
listed here produces GL_CAPABILITY_RUNNER_TAGS_UNSATISFIED and a
non_executable plan; the hint tells you to add the tags. Tags never select
or deselect jobs.
Validation codes
| Code | Cause |
|---|---|
GL_CONTEXT_VERSION_UNSUPPORTED | version is missing or not loom.gitlab-pipeline-context.v1 |
GL_CONTEXT_TYPE_INVALID | Unknown key, wrong type, blank changes.base/head, or malformed runner_tags entry |
GL_CONTEXT_PHASE_UNAVAILABLE | A missing runner-phase default was referenced before assignment |
Error messages name the JSON pointer of the offending field and never echo variable values, so a context can be shared in a bug report after redaction.
Identity
The receipt and plan report record a selection_identity derived from the
canonical context (sorted variables, changes, and any git-resolved paths). Two
runs with the same source and context produce the same identity, which is how
loom check receipts can be replayed.
Ordinary scalar binding
Caller variables override composed root/job definitions, including empty values.
Ordinary job scalars use released server dependency expansion followed by one
Runner dollar-expansion pass. Missing runtime defaults are supplied after provider
assignment; explicit CI_PROJECT_DIR, CI_BUILDS_DIR and LOOM_PROJECT_DIR
values remain exported overrides. They do not change physical execution paths.
Rules and cache fields retain their earlier-phase availability checks. Native
workflows use the separate caller variable context.
Authored root variables accept rich value/expand/description/options
definitions; jobs accept only value/expand. Explicit caller strings replace
the whole effective definition, including its expansion mode, and are not
restricted by authored options. Missing/null rich values become empty; false/null
expand preserves literal bytes. See rich variables
for authored typing, composition and consumer phases.