# glint — feature reference This document describes every capability in the current release. For planned work see [ROADMAP.md](ROADMAP.md). --- ## Lint rules Every finding carries a stable rule ID (`GL001` – `GL043`) that can be used to suppress, filter, or look up the check. Run `glint explain ` for a description, bad-YAML example, and fix. ### Pipeline-level | ID | Sev | Rule | |----|-----|------| | GL001 | WARN | No `stages:` block — GitLab falls back to its built-in default stages | | GL002 | ERR | `workflow.rules[*].when` must be `always` or `never` | | GL036 | ERR | `default.timeout` is not a valid GitLab CI duration string | | GL040 | WARN | A stage name appears more than once in `stages:` | ### Job structure | ID | Sev | Rule | |----|-----|------| | GL003 | ERR | Job missing required `script:` (or `run:`) | | GL004 | ERR | Job `stage:` references a stage not declared in `stages:` | | GL005 | ERR | `only:` and `rules:` used together | | GL006 | ERR | `except:` and `rules:` used together | | GL007 | WARN | `only:` / `except:` used (deprecated; prefer `rules:`) | ### Keyword constraints | ID | Sev | Rule | |----|-----|------| | GL008 | ERR | `when:` has an invalid value | | GL009 | ERR | `when: delayed` without `start_in:` | | GL010 | ERR | `start_in:` set but `when:` is not `delayed` | | GL011 | ERR | `parallel:` integer not in range 2–200, or map form missing `matrix:` | | GL012 | ERR | `retry:` integer not in range 0–2, or `retry.max` out of range | | GL013 | ERR | `retry.when:` contains an unrecognised failure type | | GL014 | ERR | `allow_failure:` is not a boolean or a map with `exit_codes:` | | GL015 | ERR | `interruptible:` is not a boolean | | GL016 | ERR | Trigger job also defines `script:` | | GL017 | ERR | `trigger:` map missing `project:` or `include:` | | GL018 | ERR | `coverage:` is not a regex wrapped in `/…/` | | GL019 | ERR | `release:` missing required `tag_name:`, or is not a map | | GL020 | ERR | `environment.url` set without `environment.name`, or invalid `action:` | | GL021 | ERR | `artifacts.when` invalid, or `expose_as` set without `paths` | | GL022 | WARN | `pages` job `artifacts.paths` does not include `public/` | | GL023 | ERR | `cache.when` or `cache.policy` has an invalid value | | GL024 | ERR | `rules[*].when` has an invalid value | | GL025 | ERR | `image:` map form missing `name:` | | GL026 | ERR | `inherit.default` / `inherit.variables` is not a boolean or list | | GL034 | ERR | `services:` map form missing `name:`, or `alias:` is not a valid DNS label | | GL036 | ERR | `timeout:` is not a valid GitLab CI duration string | | GL037 | ERR | `id_tokens:` entry missing required `aud:` | | GL038 | ERR | `secrets:` entry missing a provider key (`vault`, `gcp_secret_manager`, `azure_key_vault`) | | GL039 | WARN | Job uses `pages:` keyword but `artifacts.paths` doesn't include the publish directory | | GL041 | WARN | `cache.key.files` entry looks like a glob; must be an exact file path | ### Cross-job graph | ID | Sev | Rule | |----|-----|------| | GL027 | ERR/WARN | `needs:` references a job that doesn't exist (WARN when `optional: true`) | | GL028 | ERR | `needs:` references a job in a later stage | | GL029 | ERR | Circular dependency in `needs:` graph | | GL030 | ERR | `dependencies:` references a job that doesn't exist | | GL031 | ERR | `dependencies:` references a job in the same or a later stage | ### Expression & reachability | ID | Sev | Rule | |----|-----|------| | GL032 | WARN | `rules:if:` references `$VAR` not declared in any `variables:` block (may be false-positive for project-setting variables) | | GL033 | WARN | Every rule in `rules:` has `when: never` — job permanently excluded (provable without evaluating `if:` expressions) | | GL035 | WARN | `rules:changes` / `rules:exists` path is absolute — absolute paths never match in GitLab CI | | GL042 | WARN | Every `rules:if:` condition evaluates to false given the declared variable values — job statically unreachable (only fires when all referenced variables are declared in YAML) | ### Inheritance | ID | Sev | Rule | |----|-----|------| | GL043 | WARN | `inherit: default:` declared but the pipeline has no `default:` block (dead declaration), or the list form names fields not set in `default:` | ### Hidden jobs Jobs whose name starts with `.` are reusable templates; most rules are skipped for them. This matches GitLab CI's own behaviour. --- ## Include resolution | Capability | Notes | |------------|-------| | `include: local:` | Read from disk, recursively merged before linting | | `include: remote:` | HTTPS URL fetched unauthenticated; unreachable URLs produce a warning, linting continues | | `include: project:` | Fetched from the GitLab REST API using the configured token; skipped with a warning when no token is available | | `include: component:` | Fetched from the GitLab CI/CD Catalog; public components work without a token | | `include: inputs:` | `$[[ inputs.KEY ]]` and `$[[ inputs.KEY \| default(…) ]]` placeholders substituted from the `with:` block before parsing | | Depth limit | Include chains capped at 100 levels (matches GitLab); circular cross-file references detected via visited-set tracking | | Offline mode | `--offline` serves all remote includes from the cache; missing entries produce a warning | | Include cache | `--cache-dir DIR` (or `~/.cache/glint` by default with `--offline`) persists fetched templates; keyed by SHA-256 of the request coordinates | **Token resolution order** (first non-empty wins): | Source | Header | |--------|--------| | `--token` flag / `GITLAB_TOKEN` env | `PRIVATE-TOKEN` | | `CI_JOB_TOKEN` env | `JOB-TOKEN` | | `GITLAB_PRIVATE_TOKEN` env | `PRIVATE-TOKEN` | **URL resolution order:** `--gitlab-url` flag → `CI_SERVER_URL` env → `GITLAB_URL` env → `https://gitlab.com` --- ## Context simulation Pass `--branch`, `--tag`, `--source`, or `--var` to evaluate `rules:if:` and `only`/`except` filters against a specific pipeline event. When no context flag is given glint defaults to `--branch main --source push` so that `rules:if:` expressions are always evaluated. **Evaluated at lint time:** - `rules:if:` — full expression language: `==`, `!=`, `=~`, `!~`, `&&`, `||`, `!`, `(…)`, `$VAR`/`${VAR}`, string literals, `null`, regex flags (`/pat/i`) - `only:` / `except:` — ref keywords, branch-name globs, and `/regex/` patterns - `workflow:rules:` — evaluated to determine whether the pipeline would run; matching rule's `variables:` are injected before job evaluation - `rules:changes:` — path-glob patterns evaluated against the supplied changed-file list (see `--changes` / `--changes-from` flags); `*` matches within a segment, `**` crosses `/` boundaries; the extended `{paths: [...], compare_to: ...}` form is supported; without changed-file data the condition is always treated as matching (permissive) - Variable expansion — `$VAR` / `${VAR}` references in variable values expanded after all sources merge; transitive chains resolved (up to 10 passes) **Not evaluated** (no git tree at lint time): `rules:exists:`. **Predefined variables** set by shortcut flags: | Flag | Variables populated | |------|---------------------| | `--branch NAME` | `CI_COMMIT_BRANCH`, `CI_COMMIT_REF_NAME`, `CI_COMMIT_REF_SLUG`, `CI_PIPELINE_SOURCE=push` | | `--tag NAME` | `CI_COMMIT_TAG`, `CI_COMMIT_REF_NAME`, `CI_COMMIT_REF_SLUG`, `CI_PIPELINE_SOURCE=push`; clears `CI_COMMIT_BRANCH` | | `--source EVENT` | `CI_PIPELINE_SOURCE` | | `--var KEY=VALUE` | any variable; overrides shortcuts; repeatable | Use `--list-vars` to print the resolved variable table to stderr. **Changed-file flags** (for `rules:changes:` evaluation): | Flag | Effect | |------|--------| | `--changes PATH` | Mark PATH as changed; repeatable | | `--changes-from REF` | Run `git diff --name-only REF` to auto-detect changed files | --- ## Output formats Pass `--format` to `glint check`. In structured formats the summary line is written to stderr so stdout contains only the machine-readable payload. | Format | Flag | Description | |--------|------|-------------| | Text (default) | `--format text` | Ruff-style `file:line: RULE [sev] message` | | JSON | `--format json` | Stable schema (version 1); `findings` array + `summary` block | | SARIF 2.1.0 | `--format sarif` | Consumed by GitHub Code Scanning and GitLab SAST | | JUnit XML | `--format junit` | CI test-report artifact (`artifacts:reports:junit`) | | GitHub annotations | `--format github` | `::error file=…,line=…,title=RULE::message` inline PR comments | **JSON schema (`schema_version: 1`):** ```json { "schema_version": 1, "glint_version": "v0.2.20", "pipeline": ".gitlab-ci.yml", "findings": [ {"rule":"GL004","severity":"error","file":".gitlab-ci.yml","line":14, "job":"deploy","message":"stage \"production\" is not defined in 'stages'"} ], "summary": {"total": 1, "errors": 1, "warnings": 0} } ``` --- ## Configuration ### `.glint.yml` project config file Searched from the pipeline file's directory upward to the first `.git` boundary. ```yaml # Suppress rules globally for this project. ignore: - GL007 # migrating from only:/except: - GL032 # dynamic variables injected by CI # Override rule severity (error | warning | ignore). # 'ignore' is equivalent to listing the rule in ignore:. severity: GL004: warning # demote during a stage migration GL035: error # promote to hard error for this project # Extra stage names valid beyond those in the pipeline's own stages: block. stages: - quality - security # Default GitLab token (lower priority than --token and GITLAB_TOKEN). token: glpat-xxxx # Default GitLab instance URL. url: https://gitlab.example.com # Default cache directory. cache_dir: ~/.cache/glint ``` **Priority chain:** `--token`/`--gitlab-url` flags > `.glint.yml` > environment variables. ### Inline suppression (`# glint: ignore`) Suppress a finding for a specific job by placing a comment immediately before it: ```yaml # glint: ignore GL007 legacy-job: only: [main] script: echo ok # Multiple rules (comma- or space-separated): # glint: ignore GL007, GL032 other-job: script: echo ok # Suppress every rule for this job: # glint: ignore all noisy-job: script: echo ok ``` Suppressions are scoped to the single job they precede and do not affect other jobs or pipeline-level findings. --- ## Graph visualization (`glint graph`) | Mode | Output | |------|--------| | `tree` (default) | Terminal job tree: stages as branches, jobs as leaves; annotated with `[manual]`, `[delayed]`, `[trigger]` where applicable | | `includes` | Mermaid flowchart to stdout; colour-coded nodes by include type (local, remote, project, component, template) | | `pipeline` | GitLab CI-style SVG/PNG written to `--out` directory (default: `glint-out/`); converted to PNG when `rsvg-convert`, `inkscape`, or `magick` is available | | `all` | `includes` to stdout + `pipeline` file path to stderr | In DAG pipelines (any job has `needs:`) the pipeline graph uses job-to-job Bézier connectors instead of stage-to-stage lines. --- ## Developer tools | Tool | Description | |------|-------------| | `glint explain ` | Print description, rationale, bad-YAML example, and fix for a rule. Case-insensitive (`gl007` = `GL007`). | | `glint explain` | List all rules with ID, severity, and title. | | `--list-vars` | Print all resolved pipeline variables (pipeline + workflow rules + context) to stderr before linting. | | `--version` / `-v` | Print the compiled version string. |