A fast GitLab CI/CD configuration linter for .gitlab-ci.yml files. Two-stage validation: local YAML parsing and optional GitLab API validation.
This project is developed with AI assistance (Claude, Z.ai).
- Local validation — YAML syntax checking without API calls
- Optional API validation — Full semantic validation against a GitLab instance
- Smart file discovery — Auto-discover
.gitlab-ci.ymlin current and parent directories, recursive directory scanning - Multiple output formats — Text, JSON, YAML
- Flexible configuration — Config file, environment variables, CLI flags
- Single binary — No external dependencies (Go)
- Cross-platform — Linux, macOS, Windows
- Interactive setup —
setupwizard to save instances and tokens
Linux / macOS (install script):
curl -sSL https://github.com/InkyQuill/gitlab-ci-lint/raw/main/install.sh | bashThe script installs the binary to ~/.local/bin. If that directory is not in your PATH, add export PATH=$HOME/.local/bin:$PATH to your shell profile and run source ~/.bashrc (or ~/.zshrc).
With Go installed:
go install github.com/InkyQuill/gitlab-ci-lint/cmd/gitlab-ci-lint@latestThe binary will be in $(go env GOPATH)/bin (typically ~/go/bin). Ensure that directory is in your PATH.
For API validation you need a GitLab instance URL and a personal access token. Run the setup wizard:
gitlab-ci-lint setupSettings are saved to ~/.tools-config/.gitlab-ci-lint/config.yaml. Without configuration, only local YAML validation is available (use --skip-api or run without a token).
# Local validation only (no API)
gitlab-ci-lint --skip-api .gitlab-ci.yml
# Auto-discover file in current and parent directories
gitlab-ci-lint
# Full validation via API (requires instance + token from setup or env)
gitlab-ci-lint .gitlab-ci.ymlRecommended: downloads the latest release for your platform and installs to ~/.local/bin.
curl -sSL https://github.com/InkyQuill/gitlab-ci-lint/raw/main/install.sh | bashRun from any writable directory (e.g. ~/Downloads or /tmp).
VERSION=$(curl -s https://api.github.com/repos/InkyQuill/gitlab-ci-lint/releases/latest | grep -E '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/' | sed 's/^v//')
OS=$(uname | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m)
case "$ARCH" in x86_64) ARCH=amd64;; aarch64|arm64) ARCH=arm64;; i386|i686) ARCH=386;; *) echo "Unsupported: $ARCH"; exit 1;; esac
curl -sL "https://github.com/InkyQuill/gitlab-ci-lint/releases/download/v${VERSION}/gitlab-ci-lint_${VERSION}_${OS}_${ARCH}.tar.gz" | tar xz -O gitlab-ci-lint > gitlab-ci-lint
chmod +x gitlab-ci-lint
mkdir -p ~/.local/bin
mv gitlab-ci-lint ~/.local/bin/
# Add to PATH if needed
[[ ":$PATH:" != *":$HOME/.local/bin:"* ]] && echo 'export PATH=$HOME/.local/bin:$PATH' >> ~/.bashrc
export PATH=$HOME/.local/bin:$PATH$version = (Invoke-RestMethod -Uri "https://api.github.com/repos/InkyQuill/gitlab-ci-lint/releases/latest").tag_name -replace '^v', ''
$zip = "gitlab-ci-lint_${version}_windows_amd64.zip"
Invoke-WebRequest -Uri "https://github.com/InkyQuill/gitlab-ci-lint/releases/download/v$version/$zip" -OutFile $zip -UseBasicParsing
Expand-Archive -Path $zip -DestinationPath . -Force
New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\bin" | Out-Null
Move-Item -Path "gitlab-ci-lint.exe" -Destination "$env:USERPROFILE\bin\" -Force
Remove-Item $zip
$binPath = "$env:USERPROFILE\bin"
$path = [Environment]::GetEnvironmentVariable("Path", "User")
if ($path -notlike "*$binPath*") {
[Environment]::SetEnvironmentVariable("Path", "$path;$binPath", "User")
$env:Path += ";$binPath"
}Requires Go 1.24+.
git clone https://github.com/InkyQuill/gitlab-ci-lint.git
cd gitlab-ci-lint
make build # binary: ./build/gitlab-ci-lint
make install # install to $(go env GOPATH)/bingo install github.com/InkyQuill/gitlab-ci-lint/cmd/gitlab-ci-lint@latest
# Binary at $(go env GOPATH)/bin/gitlab-ci-lint
export PATH=$PATH:$(go env GOPATH)/binPriority (low to high): defaults → config file → GCL_* environment variables → CLI flags.
Default path: ~/.tools-config/.gitlab-ci-lint/config.yaml. Created by gitlab-ci-lint setup.
Example (single instance and shared options):
gitlab:
instance: "https://gitlab.com"
timeout: 30s
auth:
token: "" # prefer GCL_TOKEN env var
netrc: false
validation:
skip_api: false
strict: true
output:
format: "text" # text | json | yaml
verbose: false
color: "auto" # auto | always | never
files:
search_parent: true
max_depth: 10
ignore_patterns:
- ".git"
- "node_modules"
- "vendor"
- "build"
- "dist"
- "*.tar.gz"Project for project-specific validation is set only via GCL_PROJECT or --project, not stored in the config file.
| Variable | Description |
|---|---|
GCL_INSTANCE |
GitLab instance URL |
GCL_TOKEN |
Personal access token (scope api) |
GCL_PROJECT |
Project for API validation (ID or path) |
GCL_OUTPUT_FORMAT |
Output format: text, json, yaml |
GCL_SKIP_API |
true = local validation only |
GCL_VERBOSE |
true = verbose output |
GCL_COLOR |
auto, always, never |
GCL_DEBUG |
true = debug output |
GCL_CONFIG |
Path to config file |
For API validation you need a personal access token with api scope:
- GitLab → User Settings → Access Tokens
- Create a token with
apiscope - Provide via:
GCL_TOKEN,gitlab-ci-lint setup, configauth.token, or--token
gitlab-ci-lint [flags] [file]Commands: setup (interactive config), version, help.
Main flags:
-c, --config— config file path-t, --token— GitLab token--netrc— use~/.netrc--instance— GitLab URL (default https://gitlab.com)--project— project for API validation (ID or group/project)-s, --skip-api— local validation only-o, --output— format: text | json | yaml-v, --verbose— verbose output--color— auto | always | never-f, --file— path(s) to file(s), repeatable-d, --directory— directory to scan recursively for CI files--list-instances— list configured instances and exit
File discovery order when no file argument is given: -f flags → -d flags → single positional argument → auto-discover in current and parent directories.
# Auto-discover .gitlab-ci.yml in current and parent dirs
gitlab-ci-lint
# Multiple files
gitlab-ci-lint -f .gitlab-ci.yml -f ci/frontend.yml
# Recursive directory scan
gitlab-ci-lint -d ./monorepo
# Syntax only
gitlab-ci-lint --skip-api .gitlab-ci.yml
# Custom instance
gitlab-ci-lint --instance https://gitlab.example.com .gitlab-ci.yml
# JSON for pipelines
gitlab-ci-lint --output json .gitlab-ci.yml | jq .valid
# Project-specific (extends, trigger, etc.)
gitlab-ci-lint --project mygroup/myproject .gitlab-ci.yml
# From stdin
cat .gitlab-ci.yml | gitlab-ci-lint -0— all validations passed1— runtime error (file not found, network, auth)10— CI configuration invalid
For batch validation, exit code is 10 if any file is invalid.
make build # build to ./build/gitlab-ci-lint
make test-unit # unit tests
make test-integration # integration tests (requires built binary)
make lint
make fmtMIT — see LICENSE.
- Two-stage validation (local + API)
- Interactive setup and multi-instance config
- Text/JSON/YAML output
- Unit and integration tests
- Semantic-release and goreleaser
Plans: ROADMAP.md.