Skip to main content

GitLab keyword compatibility

This page is generated from the GitLab frontend's keyword tables in libs/loom-check (run pnpm run gitlab:compat:write). Do not edit it by hand.

Statuses: executable compiles and runs locally; resolved is consumed while composing the document; diagnosed is accepted but reported through a capability or limit diagnostic; preserved stays in the document and is reported as GL_SOURCE_UNSUPPORTED when a selected job or the root uses it.

Root keywords​

KeywordStatusNotes
after_scriptresolvedfolded into default: during resolution
before_scriptresolvedfolded into default: during resolution
cacheresolvedfolded into default: during resolution
defaultresolvedtwelve released default keywords are filtered by inherit and copied whole when unspecified; downstream support varies
hookspreservedpre_get_sources_script has no local equivalent
imageresolvedfolded into default: during resolution
includeresolvedlocal: literal/star includes compose ordered selected entry bytes (symlinks contribute target text); public HTTP(S) remote sources support integrity, TTL cache and file-scoped inputs; project, template and component includes are diagnosed
servicesresolvedfolded into default: during resolution
specresolvedfile-scoped typed inputs bind before composition; broader component/module inputs remain unsupported
stagesexecutableorders job phases; pipelines whose included jobs are only .pre/.post do not run
variablesexecutablescalar/rich values use released conversion and raw controls; root metadata validates authored defaults; one Runner environment pass and separate rule/cache phases
workflowexecutableworkflow:rules decide whether the pipeline runs

Job keywords​

KeywordStatusNotes
after_scriptexecutableruns in a fresh shell after script; ordinary finalizer failures do not replace the job outcome
allow_failureexecutablejob failure does not fail the pipeline
artifactsexecutablepaths are exported from the workspace; retention is not enforced
before_scriptexecutableruns before script in the same shell
cacheexecutablekeys and paths are restored and saved locally
coverageexecutablethe regex is accepted; report processing is unavailable
environmentdiagnosedscripts run; deployment tracking, protection, scoped secrets, lifecycle actions and predefined variables are unavailable
id_tokensdiagnosedno OIDC issuer exists locally
imageexecutableselects the Docker image
inheritresolvedfilters default keywords and YAML root variables independently; explicit parent/job and caller values survive
interruptibleexecutablecancellation is ignored locally
needsexecutableorders execution; same-pipeline transfers restore available archives
pagesdiagnoseddefault public output is collected as local artifacts; hosting, URL variables, path prefixes and expiry are unavailable; custom publish is not executable
parallelexecutableInteger 1..200 expands ordinary native instances with source/exact selection and runtime node facts. Matrix expansion remains required and unsupported.
resource_groupdiagnosedno cross-run locking exists locally
rulesexecutableif, changes, when, allow_failure, needs and job variable actions are evaluated
scriptexecutableruns in the job container or on the host
servicesexecutableservice containers are started; privileged services need --privileged-service
stageexecutableassigns the job to a stage
tagsdiagnosedrunner tags must be listed in the context runner_tags
variablesexecutablecaller strings replace whole root/job definitions, including empty values and raw mode

default: keywords​

KeywordStatusNotes
after_scriptexecutablecopied whole when selected and unspecified, including null; runs as finalization
artifactsexecutablecopied whole; report processing and retention remain unavailable
before_scriptexecutablecopied whole when selected and unspecified, including null; runs as setup
cacheexecutablecopied whole; job cache mappings replace this default
hookspreservedcopied whole; selected hooks remain unsupported
id_tokensdiagnosedcopied whole; local issuance remains unavailable
imageexecutablecopied whole; existing image validation applies
interruptibleexecutablecopied whole; cancellation is ignored locally
retrypreservedcopied whole; selected retry remains unsupported
servicesexecutablecopied whole; existing service validation and capability policy apply
tagsdiagnosedcopied whole; runner tag capability policy applies
timeoutpreservedcopied whole; selected timeout remains unsupported

rules: keys​

KeywordStatusNotes
allow_failureexecutableoverrides the job's allow_failure when the rule matches
changesexecutablelist shorthand or exactly one of paths/regexp, optionally with compare_to; globs support brace alternation, character classes and escaping; regexp subset below
existspreservednot evaluated locally
ifexecutableGitLab rule expressions over pipeline variables
needsexecutablereplaces the job's needs when the rule matches
variablesexecutablejob-only scalar assignments replace whole definitions after first match; caller values win, including empty; workflow actions remain unsupported
whenexecutableper-scope allowlists below; on_failure and delayed are preserved but not executable

Composition and inheritance​

Local star patterns expand with released ordered RE2 quoting/replacements: **/, then **, then *. Other glob punctuation is literal. Local paths remove only leading slashes and preserve authored spaces for literals and patterns; matching uses canonical source names independently of authored directory spelling. Wildcard reads cannot use the explicit external literal/source exception. Matches retain Git tree order and repeated occurrences; no match adds no include. The generated filter has a 1000-byte bound, not a 1000-match cap. The current included same-project local parent is skipped; a matching top-level root can be included once before that rule applies. Literal self-includes retain deterministic cycle rejection. Git-backed compilation reads selected snapshot origins, including dirty/eligible untracked sources and retained HEAD bytes while excluding ignored dependency/runtime YAML. GitLab local includes read exact selected entry bytes, including symlink target text as YAML without following the target; missing logical children beneath links remain missing. Root-file authoring admission retains its separate contained-link policy. Valid unborn repositories support check/compile; local execution still requires HEAD. Non-Git roots use bounded rooted discovery. Added discovery has a local 64 Mi-unit guard; existing Git scans and incomplete upstream include-limit parity remain separate.

Public HTTP(S) remote includes and URL shorthand share native source retrieval, optional strict-base64 SHA256 integrity, opt-in raw-content TTL reuse and file-scoped inputs. Nested remote includes are anonymous and cannot read caller-local files. Every occurrence verifies integrity and binds independently; the hash-only source closure pins requested/final address and content identities across preflight and isolated compilation. The source cache is separate from job caches. See public remote sources for cache clearing forms, containment and cancellation. Project/template/component sources and variable URLs remain unsupported.

Include rules support ordered ordinary if predicates and when: always/never before selected-source validation, wildcard expansion, reads and target input binding. The first matching rule wins; omitted actions include and no match excludes. Caller lookup preserves raw explicit context strings, undefined versus empty, and excludes YAML root/job variables; nested remote lookup is anonymous. Absent/null/false rule lists bypass selection; empty lists/maps and empty individual rules fail. Null if/when fields are compacted away. Every outer rule validates before selection, including later rules behind an unconditional match. Include exists/changes, variable addresses, full phase/provenance and released include-limit parity remain required future work.

Local includes merge before references, then ordered hidden or executable parents. Later parents win, then the child; mappings merge recursively and arrays replace. Executable parents retain their own job identity; reserved root mappings can also be parents without becoming jobs. Omitted, null or empty-list extends selects no parents. Missing/nonmapping parents, cycles and more than eleven inheritance edges fail.

inherit.default and inherit.variables independently accept booleans or name lists. Omitted/null controls select all; empty lists select none. Only YAML root defaults/variables are filtered: explicit parent/job values and supplied context survive. Job rules, cache expansion and runtime variables share the filter. Workflow predicates still see their unfiltered root/context scope. Workflow-rule variable assignments remain unsupported.

All twelve configured defaults are source-validated before inheritance filtering, even when every job filters or overrides them. Validation follows the released default profile: four raw top-level cache entries and two cache-key files with the increased-file-limit flag disabled; hosted settings/flags can differ. The twelve default keywords above copy as whole keywords when selected and the composed job value is omitted or null. Explicit empty lists/maps or false remain specified and use the keyword's own validator. A child null can therefore discard a parent value and receive a default again. Legacy root image, before_script, after_script, cache and services fold into defaults; two specified values conflict, while null is unspecified. Recognizing and copying a default does not implement its downstream behavior: hooks, retry and timeout remain unsupported; other capability limitations remain visible.

Rule expressions​

  • Operators: !, ==, !=, =~, !~, &&, ||. Expressions retain scalar/null/boolean types and support reversed/grouped operands and unary !. Regex operands accept literal, quoted and variable-held /pattern/flags forms.
  • workflow:rules accept when: always, never.
  • Job rules accept when: always, manual, never, on_success; on_failure and delayed are preserved but not executable.
  • Integer collision winners are fixed before job rules. Job contexts receive structural CI_NODE_INDEX/CI_NODE_TOTAL defaults; workflow contexts do not. Use direct predicates for these facts.
  • Integer base needs expand source aliases before job rules. Every normalized base list and literal rules:needs list validates before job/request pruning; duplicate final names fail regardless of optional/artifact flags. Rule overrides name literal final IDs and do not expand aliases.
  • If-variable lookup uses raw winning root/job/caller strings; it does not recursively expand aliases or pattern contents. Undefined variables evaluate as null; changes without paths in the context are resolved with git diff --merge-base.

Changed-path regular expressions​

rules:changes:regexp supports ASCII literals, ordinary character classes, alternation, capturing/noncapturing groups, quantifiers, nested negative lookahead, and absolute \A/\z anchors. It matches any changed path; an empty changed-path list does not match. Use exactly one of paths and regexp; either can use compare_to.

Existing variables expand in the rule phase with global, job, then context precedence; undefined references stay literal. Raw and expanded patterns are limited to 255 characters. A finite per-path match timeout produces a source-invalid, rule_invalid result rather than guessing a selection. The timeout is approximate and does not claim GitLab's exact timing behavior.

This is a limited Ruby-compatible syntax surface, not complete Ruby regex support. Line anchors ^/$, inline flags, lookbehind, positive lookahead, backreferences, omitted-minimum quantifiers, class extensions, Unicode pattern syntax and unsupported escapes are rejected. Use absolute anchors for filenames containing newlines. Existing rules:if expressions and glob matching keep their RE2 behavior.

YAML tags​

  • !reference [job, key, ...] resolves against the merged catalog after include: and before extends:. It preserves scalar, mapping and sequence targets. Cycles and missing/null/false targets are rejected; empty string/map/list targets remain subject to the consuming keyword's validation. String path segments may be empty. No other explicit YAML tag is accepted.

  • References have no independent ten-hop limit; concrete copies consume the shared YAML materialization budget. before_script, script and after_script flatten nested command arrays up to ten array levels, preserving order and string bytes. Services, cache and needs normalize their own composable arrays; job rules and stages enforce their own ten-array-level limit. Tags, paths and service entrypoints remain flat string arrays.

  • An authored script must be present and non-blank: a blank scalar or top-level [] is invalid. An array containing an empty string is valid; [[]] is a valid zero-command main phase. Optional before/after commands may be null or empty. Native required scripts retain their nonempty-command requirement.

Diagnostic codes​

CodeMeaningFix hint
GL_SOURCE_SYNTAXthe YAML could not be parsed—
GL_SOURCE_COMPOSITIONextends, include or anchors produced an invalid documentthe extended template must be a mapping; hidden keys that nothing extends are ignored, as in GitLab
GL_SOURCE_LIMITa source or rule matching limit was exceeded—
GL_SOURCE_DOCUMENTthe document is not a GitLab CI mapping—
GL_SOURCE_TYPEa keyword has the wrong type or an unsupported valuedefine the job as a mapping of GitLab keywords such as script and stage; a list or scalar is not a job
GL_SOURCE_UNSUPPORTEDa keyword or include source is preserved but not executable locallyuse include: local: <path> or a public HTTP(S) remote; project, template and component includes are not resolved locally; use when: on_success, never, manual or always, or omit when to keep the GitLab default
GL_CONTEXT_PHASE_UNAVAILABLEa runner-phase variable was supplied or referenced before it exists—
GL_VARIABLE_CYCLEvariable expansion loops—
GL_VARIABLE_EXPANSION_UNSUPPORTEDan expansion form the local runtime cannot evaluateremove the $ from the cache key (GitLab writes $$ for a literal $) or move the value into a pipeline variable
GL_VARIABLE_EXPANSION_LIMITexpansion exceeded the size or substitution budget—
GL_RULE_REGEX_UNSUPPORTEDa literal rule regex has invalid or unsupported syntax—
GL_RULE_CHANGES_UNAVAILABLErules:changes could not be resolved from git—
GL_PIPELINE_SPECIAL_STAGES_ONLYrule-included jobs are only .pre/.post, so the pipeline cannot run—
GL_RULE_MANUAL_BLOCKINGa blocking manual job stops the local pipeline—
GL_CAPABILITY_RUNNER_TAGS_UNSATISFIEDjob tags are missing from the context runner_tagsadd the job's tags to runner_tags in the --context file
GL_CAPABILITY_PRIVILEGED_SERVICE_NOT_ALLOWEDa privileged service alias needs --privileged-servicepass --privileged-service <alias> to loom run
GL_CAPABILITY_HOSTED_MACOS_UNAVAILABLESaaS macOS runners have no local equivalent—
GL_CAPABILITY_ID_TOKENS_UNAVAILABLEid_tokens cannot be issued locally—
GL_CAPABILITY_PAGES_UNAVAILABLEcustom Pages publish directories are not executable locally—
GL_CAPABILITY_RESOURCE_GROUP_UNAVAILABLEresource_group locking is not enforced locally—
GL_LIMIT_OPTIONAL_MANUAL_NOT_EXECUTEDoptional manual jobs are skipped—
GL_LIMIT_WORKFLOW_AUTO_CANCEL_IGNOREDworkflow:auto_cancel is ignored—
GL_LIMIT_INTERRUPTIBLE_IGNOREDinterruptible cancellation is ignored—
GL_LIMIT_PAGES_DEPLOYMENT_UNAVAILABLElocal public artifacts do not create a Pages deployment or hosted variables—
GL_LIMIT_ENVIRONMENT_TRACKING_UNAVAILABLEenvironment tracking, protection, scoped secrets, lifecycle actions and predefined variables are unavailable; caller variables are unchanged—
GL_LIMIT_REPORT_PROCESSING_UNAVAILABLEartifact reports are exported but not processed—
GL_LIMIT_ARTIFACT_RETENTION_NOT_ENFORCEDexpire_in is not enforced—
GL_LIMIT_WHEN_ALWAYS_NOT_ENFORCEDwhen: always runs as on_success locally—