Serialization 04 ๐ Integrating Validation Workflows into Git Hooks and CI/CD Pipelines
Validation that runs only when a developer remembers to run it is not validation; it is a suggestion. The point of integrating schema and syntax checks into Git hooks and CI/CD pipelines is to make invalid configuration impossible to commit and impossible to merge. The failure mode is silent otherwise: a YAML file with a duplicate key or a TOML file with a misplaced table header parses on the author’s machine, passes review, and breaks at deploy time when the application reads a value that was never set.
This chapter covers the two enforcement layersโpre-commit hooks for immediate feedback and CI jobs for the gateโand the tooling that connects them. It explains why pre-commit hooks are fast and local while CI is authoritative, how to wire YAML and TOML validators into both, and the single-binary tools that replace a stack of format-specific linters. The sections that follow walk through the pre-commit framework, the CI job patterns for GitHub Actions and GitLab CI, and the unified validator that checks syntax, schema, and formatting in one pass.
Key point: Pre-commit hooks catch invalid configuration before it enters history; CI jobs catch anything that bypassed the hooks, and both layers should run the same validators with pinned versions so local and remote results agree.
Why validation workflows exist
The commit-time problem. A developer edits a config.yaml file, removes a colon by accident, and commits. The YAML parser on their machine may not run because the editor does not validate on save. The commit enters history, the branch is pushed, and CI runs the testsโbut the tests read a different fixture, or the application starts with a fallback configuration. The error surfaces in staging or production, where the cost of diagnosis is hours instead of seconds. A pre-commit hook that runs a syntax check on every staged file reduces that cost to the moment of commit.
The gate problem. Hooks can be bypassed with git commit --no-verify, skipped when a developer does not run pre-commit install, or omitted from a new contributor’s setup. CI is the authoritative layer because it runs on the server, cannot be bypassed locally, and produces a status check that branch protection can require. The two layers are complementary: hooks provide fast feedback, CI provides the guarantee.
The tool proliferation problem. A project that validates YAML, TOML, JSON, and INI has historically needed separate tools: yamllint for YAML, taplo for TOML, jsonlint for JSON, and something else for INI. The config-file-validator (cfv) tool consolidates these into a single static binary that validates 18 formats, enforces schemas, and checks formatting in one pass, reading existing .prettierrc, taplo.toml, .yamlfmt, and .editorconfig files so it respects the project’s established conventions . For projects already using Prettier and Taplo, the consolidation is optional; for projects maintaining a stack of format-specific linters, it is a simplification.
a. Pre-commit hooks for YAML and TOML
The pre-commit framework runs configured hooks on staged files before a commit is created. Installation is a one-time step per developer: pre-commit install writes the hook script into .git/hooks/pre-commit. The configuration lives in .pre-commit-config.yaml at the repository root and is version-controlled, so every contributor runs the same checks.
The simplest YAML and TOML validation uses the pre-commit-hooks repository, which provides check-yaml and check-toml for syntax validation . These hooks parse the file and fail if the syntax is invalid; they do not check formatting or schema.
# .pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: check-yaml # validate YAML syntax
- id: check-toml # validate TOML syntax
For stricter YAML validation, yamllint adds style rules, line-length limits, and duplicate-key detection. Its documentation provides the pre-commit configuration directly .
- repo: https://github.com/adrienverge/yamllint.git
rev: v1.35.1
hooks:
- id: yamllint
args: [--strict, -c=.yamllint.yml]
For TOML formatting and schema validation, taplo provides a pre-commit hook that runs taplo check or taplo fmt. The hook is available from the taplo-tools/taplo repository and can be scoped to specific files like pyproject.toml . A lefthook configuration shows the same pattern in a different hook manager: taplo check for *.toml files and actionlint for GitHub Actions workflows .
The choice between check-yaml/check-toml and the format-specific linters is about depth. Syntax hooks answer “does this parse?”; yamllint and taplo answer “does this parse and follow the project’s rules?” A project that cares about formatting consistency should use the linters; a project that only needs to prevent broken files can start with the syntax hooks.
b. CI jobs for GitHub Actions and GitLab CI
CI validation is a job that runs on every push or pull request and fails the pipeline if any configuration file is invalid. The job should install the same validators used in pre-commit, with pinned versions, and run them against the repository or the changed files.
A GitHub Actions workflow for YAML linting installs yamllint and runs it on the repository. The tool auto-detects the GitHub Actions environment and decorates the diff with linting errors when it runs .
# .github/workflows/validate.yml
name: Validate configuration
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install yamllint
run: pip install yamllint
- name: Lint YAML
run: yamllint .
For a unified check across YAML, TOML, and other formats, the validate-configs-action wraps config-file-validator and recursively scans the workspace for configuration files, reporting the path and line number of each failure . The underlying cfv binary can also be run directly in CI with a JUnit reporter for integration with test dashboards .
- uses: kehoe/validate-configs-action@v4.0.1
with:
search-paths: "."
exclude-dirs: "node_modules,vendor"
In GitLab CI, the same pattern applies: a job in the validate stage installs the validator and runs it. GitLab’s own documentation emphasizes that syntax errors prevent the pipeline from starting at all, which makes local validation and CI Lint checks important for .gitlab-ci.yml itself . For configuration files generally, a yamllint job with allow_failure: false in a merge request pipeline enforces the gate.
# .gitlab-ci.yml
yaml-lint:
stage: validate
image: python:3.12-slim
before_script:
- pip install yamllint
script:
- yamllint -c .yamllint.yml .
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
The CI job should run on pull requests and pushes to protected branches, not on every branch push, to avoid wasting compute on work-in-progress commits that will be squashed.
c. Unified validation with config-file-validator
config-file-validator (cfv) is a static Go binary that validates, formats, and enforces schemas on configuration files across 18 formats including YAML, TOML, JSON, XML, HCL, and INI . It reads existing formatter configurations (.prettierrc, taplo.toml, .yamlfmt, .editorconfig) so it applies the settings the project already uses, and it provides a --fix flag for safe automatic corrections.
# Validate all config files in the repository
cfv check .
# Validate with schema enforcement and JUnit output for CI
cfv check --reporter=junit:results.xml --schemastore .
The check command validates syntax, enforces schemas, and checks formatting in one pass, exiting with code 1 if any file fails . The --schemastore flag uses the SchemaStore catalog to validate files like pyproject.toml and Cargo.toml against their published schemas, which catches semantic errors that syntax validation misses.
For a project using cfv, the pre-commit configuration is a single hook that runs cfv check on staged files, and the CI job is a single step that runs the same command with a JUnit reporter. The consolidation reduces the number of tools to install, version, and debug, at the cost of adopting a newer tool with a single maintainer (the repository notes it is not officially supported by Boeing) .
Complete Example Session
# ============================================
# PART 1: PRE-COMMIT CONFIGURATION
# ============================================
# .pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: check-yaml # syntax check
- id: check-toml # syntax check
- repo: https://github.com/adrienverge/yamllint.git
rev: v1.35.1
hooks:
- id: yamllint
args: [--strict, -c=.yamllint.yml]
- repo: https://github.com/taplo-tools/taplo
rev: release-cli-v0.8.1
hooks:
- id: taplo-format # format TOML
files: pyproject.toml
# ============================================
# PART 2: YAMLLINT CONFIGURATION
# ============================================
# .yamllint.yml
extends: default
rules:
line-length:
max: 120
level: warning
truthy:
allowed-values: ['true', 'false']
# ============================================
# PART 3: GITHUB ACTIONS VALIDATION JOB
# ============================================
# .github/workflows/validate.yml
name: Validate
on: [pull_request, push]
jobs:
config:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.12" }
- run: pip install yamllint
- run: yamllint -c .yamllint.yml .
- uses: kehoe/validate-configs-action@v4.0.1
with:
exclude-dirs: "node_modules,vendor"
# ============================================
# PART 4: GITLAB CI VALIDATION JOB
# ============================================
# .gitlab-ci.yml
stages: [validate]
yaml-lint:
stage: validate
image: python:3.12-slim
before_script:
- pip install yamllint
script:
- yamllint -c .yamllint.yml .
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
toml-check:
stage: validate
image: rust:latest
script:
- cargo install taplo-cli --locked
- taplo fmt --check .
rules:
- changes: ["**/*.toml"]
# ============================================
# PART 5: THE UNIFIED ALTERNATIVE
# ============================================
# Install once, validate everything
# brew install config-file-validator
# cfv check --reporter=junit:results.xml --schemastore .
#
# Reads .prettierrc, taplo.toml, .yamlfmt, .editorconfig
# Validates YAML, TOML, JSON, XML, HCL, INI, and more
The parts covered the pre-commit hook configuration, the yamllint rules file, the GitHub Actions job, the GitLab CI jobs, and the unified cfv alternative.
Quick Reference
Pre-Commit Hooks
| Hook | Source | Checks |
|---|---|---|
check-yaml | pre-commit-hooks | YAML syntax |
check-toml | pre-commit-hooks | TOML syntax |
yamllint | adrienverge/yamllint | YAML style + syntax |
taplo-format | taplo-tools/taplo | TOML formatting |
CI Validators
| Tool | Formats | CI Integration |
|---|---|---|
yamllint | YAML | Native GitHub annotations |
taplo | TOML | taplo fmt --check |
cfv | 18 formats | JUnit/SARIF/GitHub reporters |
validate-configs-action | Multiple | GitHub Action wrapper |
Enforcement Layers
| Layer | Speed | Bypassable | Authority |
|---|---|---|---|
| Pre-commit | Fast (local) | Yes (--no-verify) | Developer |
| CI | Slower (server) | No | Branch protection |
Best Practices
โ Do This:
# Pin hook versions in .pre-commit-config.yaml
rev: v6.0.0 # โ
# Run the same validator locally and in CI
# yamllint -c .yamllint.yml . # โ
# Scope CI jobs to relevant paths
rules:
- changes: ["**/*.toml"] # โ
# Use --strict for yamllint in CI
yamllint --strict -c .yamllint.yml . # โ
โ Don’t Do This:
# Unpinned hook versions
rev: main # โ
# CI only, no local hook
# Developers discover errors after push # โ
# Different linter versions locally and in CI
pip install yamllint # no version pin # โ
# Validate only on push, not pull request
on: [push] # โ
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Hook not running | pre-commit install not executed | Document setup in CONTRIBUTING |
| CI passes locally but fails remotely | Different tool versions | Pin versions in CI config |
| YAML style failures block CI | yamllint defaults too strict | Add .yamllint.yml with project rules |
| TOML formatting drift | No taplo fmt hook | Add taplo-format pre-commit hook |
| Schema errors missed | Syntax-only validation | Use cfv --schemastore or validate-pyproject |
Real-World Examples
1. check-yaml and check-toml hooks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks: [check-yaml, check-toml]
2. yamllint hook with custom config
- repo: https://github.com/adrienverge/yamllint.git
rev: v1.35.1
hooks:
- id: yamllint
args: [--strict, -c=.yamllint.yml]
3. taplo hook for pyproject.toml
- repo: https://github.com/taplo-tools/taplo
rev: release-cli-v0.8.1
hooks: [{ id: taplo-format, files: pyproject.toml }]
4. GitHub Actions yamllint step
- run: pip install yamllint && yamllint .
5. GitLab CI yamllint job
yaml-lint:
script: yamllint -c .yamllint.yml .
6. Unified cfv check
cfv check --reporter=junit:results.xml .
7. SchemaStore enforcement
cfv check --schemastore .
8. validate-configs-action
- uses: kehoe/validate-configs-action@v4.0.1
9. lefthook TOML check
toml:
glob: "*.toml"
run: taplo check {staged_files}
10. Pre-commit manual run
pre-commit run --all-files
Visual
Two-Layer Enforcement
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ LAYER 1: PRE-COMMIT (LOCAL) โ
โ โ
โ git commit โ
โ โ โ
โ โผ โ
โ pre-commit hook runs โ
โ โ โ
โ โโโโโโดโโโโโ โ
โ โผ โผ โ
โ PASS FAIL โ
โ โ โ โ
โ โผ โผ โ
โ Commit Commit blocked โ
โ created Developer fixes โ
โ โ
โ Fast, local, bypassable with --no-verify โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ LAYER 2: CI (SERVER) โ
โ โ
โ Push / Pull Request โ
โ โ โ
โ โผ โ
โ CI job runs same validators โ
โ โ โ
โ โโโโโโดโโโโโ โ
โ โผ โผ โ
โ PASS FAIL โ
โ โ โ โ
โ โผ โผ โ
โ Merge Merge blocked โ
โ allowed (branch protection) โ
โ โ
โ Authoritative, cannot be bypassed locally โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Tool Selection
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ YAML AND TOML VALIDATION TOOLS โ
โ โ
โ YAML โ
โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ
โ โ check-yaml โ โ yamllint โ โ
โ โ (syntax) โ โ (style+rules)โ โ
โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ
โ โ
โ TOML โ
โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ
โ โ check-toml โ โ taplo โ โ
โ โ (syntax) โ โ (format+checkโ โ
โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ
โ โ
โ UNIFIED โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ config-file-validator (cfv) โ โ
โ โ 18 formats, schema, formatting โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
CI Job Flow
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ GITHUB ACTIONS / GITLAB CI โ
โ โ
โ Checkout code โ
โ โ โ
โ โผ โ
โ Install validators (pinned versions) โ
โ โ โ
โ โผ โ
โ Run checks โ
โ โโโ yamllint . โ
โ โโโ taplo fmt --check . โ
โ โโโ cfv check --schemastore . โ
โ โ โ
โ โผ โ
โ Report results (exit code, annotations) โ
โ โ โ
โ โโโโโโดโโโโโ โ
โ โผ โผ โ
โ PASS FAIL โ pipeline red โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
| Pre-commit framework | pre-commit with .pre-commit-config.yaml |
| YAML syntax hook | check-yaml |
| TOML syntax hook | check-toml |
| YAML linter | yamllint |
| TOML checker | taplo check / taplo fmt |
| Unified validator | config-file-validator (cfv) |
| CI reporters | JUnit, SARIF, GitHub |
| Enforcement | Pre-commit (fast) + CI (authoritative) |
Key takeaways:
- Pre-commit hooks catch errors before they enter history. The
check-yamlandcheck-tomlhooks frompre-commit-hooksprovide syntax validation with zero configuration;yamllintandtaploadd style and formatting rules . - CI is the authoritative gate. Hooks can be bypassed with
--no-verifyor skipped by a contributor who never ranpre-commit install. The CI job runs the same validators on the server and produces a status check that branch protection can require. - Pin versions in both layers. A
yamllintversion that changed between local and CI produces diffs that cannot be reproduced locally, and the CI failure becomes unexplainable without the version lock. cfvconsolidates the tool stack. One static binary validates YAML, TOML, JSON, and fifteen other formats, enforces schemas via SchemaStore, and reads existing formatter configurations so it does not fight the project’s established style .- Schema validation catches what syntax does not. A TOML file can be syntactically valid and semantically wrongโa required field missing, a type mismatch.
cfv --schemastoreandvalidate-pyprojectcheck the file against its published schema, not just its grammar.
Remember: Configuration validation has two jobs: prevent broken files from being committed, and prevent them from being merged. Pre-commit handles the first with hooks that run on every commit; CI handles the second with jobs that run on every pull request. The same tools should run in both places, with the same pinned versions, so a local pass guarantees a remote pass. When a file is syntactically valid but semantically wrongโa pyproject.toml missing a required field, a YAML workflow with an undefined actionโsyntax checks will not catch it. Add schema validation with cfv --schemastore or a format-specific validator, and make the CI job the only path to the main branch.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!