skills/yaml/SKILL.md
YAML standards for config files, Ansible playbooks, k8s manifests, GitHub Actions, docker-compose, and any project config. Built from the YAML 1.2 spec, yamllint defaults, and the practical pitfalls (Norway problem, type coercion, anchor gotchas).
npx skillsauth add abix-/claude-blueprints yamlInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
YAML 1.2 is the current spec (2009, last clarified 2021). Most tooling
implements YAML 1.1 by default for backwards compatibility, which means
old type-coercion rules (the "Norway problem" with NO, on/off)
still bite. Write for the lowest common denominator: be explicit.
For Jinja templating inside YAML, read the jinja skill.
--- separator)
only when the tool expects it (k8s manifests, helm).---. Some tools require it, most don't. Pick
one style per repo. Trailing ... end-marker is almost never needed.Naming follows the host system's convention. Don't reinvent.
| Context | Convention | Example |
|---------|------------|---------|
| Ansible | snake_case | become_user: root |
| Kubernetes | camelCase | metadata.creationTimestamp |
| GitHub Actions | kebab-case (jobs/steps), snake_case (inputs) | runs-on: ubuntu-latest |
| docker-compose | snake_case | depends_on: [db] |
| Helm values | usually camelCase | image.pullPolicy |
| OpenAPI / JSON Schema | camelCase | additionalProperties |
When in doubt, read three nearby keys and match.
YAML aggressively coerces unquoted strings. The safe rule: quote anything that could plausibly be misinterpreted.
# unquoted: usually fine
name: production
version: 1.0 # parsed as float!
count: 42
# MUST quote
ratio: "1.0" # if you want string "1.0", not 1.0
empty: "" # not null
country_code: "NO" # the Norway problem -- unquoted = bool false
country_code: "no" # same
on_event: "on" # YAML 1.1 booleans
phone: "+44 1234 5678" # starts with `+` is fine, but quote for clarity
date_str: "2026-05-11" # if you want string, not Date
ip: "10.0.0.1" # IPv4 literals are floats in some parsers
key_path: "/etc/nginx" # leading / is fine; leading {/[/&/*/etc. is not
template: "{{ var }}" # Jinja must be quoted
percent: "50%" # safe as-is but quote anyway
# Forbidden unquoted starts (will fail parse or coerce):
# { [ & * ! | > ' " % @ `
Prefer double quotes when you need escapes (\n, \t, \").
Single quotes are pure literal: only '' escapes a single quote, no
other escapes.
The Norway problem: YAML 1.1 parsers see NO, yes, no,
on, off, Y, N, True, False as booleans. YAML 1.2 drops
this but most libraries default to 1.1 behavior. Always use
true / false. Quote anything that looks like a YAML 1.1 boolean
if you mean a string.
42 integer, 1.5 float, 1e6 scientific, 0xff hex, 0o17 octal
(1.2) / 017 octal (1.1).mode: 0644 -> 420 decimal. File
modes must be quoted strings: mode: "0644"..NaN, .inf, -.inf are special floats._ digit separators (1_000_000) are NOT standard in YAML 1.2;
some parsers accept them.true / false. Use these. Lowercase.null. Explicit. ~ is the legacy alternative; avoid.key: parses as null in most parsers. Don't rely on
it. Write key: null if you mean null, key: "" for empty
string.Five styles. Pick the one that matches the consumer's needs:
# `|` literal: preserve newlines, strip final
script: |
set -euo pipefail
echo "hello"
# `|-` literal strip: no final newline
script: |-
one
two
# `|+` literal keep: preserve all trailing newlines
script: |+
one
# `>` folded: newlines become spaces, blank lines preserved
desc: >
This is a long
description that
collapses to one line.
# `>-` folded strip: no final newline
desc: >-
paragraph one.
# Plain multi-line (with continuation)
title: This is
one logical
line.
| keeps the structure verbatim. Use for scripts, regex, exact
whitespace.> collapses to a paragraph. Use for prose / long descriptions.|, |-, |+) controls trailing newlines.# block style (preferred)
servers:
- name: a
ip: 10.0.0.1
- name: b
ip: 10.0.0.2
# flow style (for short inline)
tags: [prod, web, us-east]
labels: {env: prod, tier: frontend}
- item block style for diff
friendliness.defaults: &defaults
retries: 3
timeout: 30
prod:
<<: *defaults
host: prod.example.com
stage:
<<: *defaults
host: stage.example.com
&name defines an anchor, *name references it.<<: *name merge key (YAML 1.1 only). Removed in YAML 1.2 but
widely supported. Helm and kustomize support it; some tooling
doesn't.# Single-line comment.
servers:
- name: prod # inline comment
timeout: 30 # in seconds
# to end of line. No block comments.#.- name: Set port
ansible.builtin.set_fact:
port: "{{ env_port | int }}" # string -> int
enabled: true # not "yes"
mode: "0644" # quoted! avoid octal trap
when: env == "prod" # already Jinja; don't double-template
"0644".true / false; ansible-lint flags yes/no.when: clauses are already Jinja; no {{ }} needed.apiVersion: v1
kind: ConfigMap
metadata:
name: my-config
labels:
app: backend
data:
config.json: |
{
"key": "value"
}
apiVersion, containerPort). Required by the
API server.---. Helm renders these.| literal block.memory: "256Mi", cpu: "500m") as quoted
strings to avoid float coercion of plain numbers.name: CI
on:
push:
branches: [main]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run tests
run: |
cargo test
on: is fine unquoted in Actions despite being a YAML 1.1 boolean
(the parser is aware).names are free-form.uses: references with @v4 or @<commit-sha>. SHA-pin for
security.run: scripts get | literal block. Multi-line bash works
here.version: "3.9" # quote; YAML treats 3.9 as float
services:
web:
image: nginx:1.27.0 # pin; no :latest
ports:
- "8080:80" # quote ports (1.1 bool: NO)
depends_on:
- db
: parsing oddities.:latest is operational debt. Always pin.YAML parse speed rarely matters, but a few things do:
libyaml C bindings
(pip install pyyaml[c] or just pyyaml if available) so
yaml.CSafeLoader is usable. 10x faster than the pure-Python
loader.yaml.load() in Python (PyYAML) executes arbitrary code.
Always use yaml.safe_load(). Same applies to Ruby's Psych.load
(vs safe_load).!!python/object) can execute code.yamllint on every change. Standard config catches most
issues. Wire into pre-commit:
yamllint -d "{extends: default, rules: {line-length: {max: 120}}}" .
kubeval, kubeconform, kustomize build | kubectl apply --dry-run=serverajv-cli or check-jsonschemayq for YAML in scripts (jq syntax). yq eval '.servers[0]' file.yml.1.0, 2.10 are floats; quote.no, yes, on, off,
Y, N, True, False, NULL.0644 becomes 420 decimal.version: 042 is 34 decimal in
YAML 1.1.key: when you mean empty string. Be explicit:
key: "" or key: null.development
--- name: ueforge description: ueforge framework: the base layer every UE4SS Rust mod in the Grounded2Mods workspace builds on. Authoritative on the composition model (Effect/Trigger/Skill), the Def/Registry/Instance/Controller pattern, hot reload, discovery, hardening doctrine, and the five framework modules (rpg, stacks, difficulty, inventory, damage). Use when writing or modifying code under `ueforge/` in [abix-/Grounded2Mods](https://github.com/abix-/Grounded2Mods), or when promoting a patte
tools
TypeScript and JavaScript standards. Sourced from [abix-/chromium-extensions](https://github.com/abix-/chromium-extensions) (Hush + filter-anything-everywhere). Use when writing TS/JS, including browser extension bootstrap shims, MV3 service workers, and small web frontends.
development
--- name: schedule1 description: Modding Schedule 1 (TVGS, IL2CPP Unity + MelonLoader + Harmony). Authoritative on Schedule 1 game specifics: engine type, MelonLoader/Il2CppInterop references, eMployee mod root-cause findings, vanilla CookRoutine + StartMixingStationBehaviour internals, certainty-tracking discipline. Mod code lives in [`abix-/Schedule1Mods`](https://github.com/abix-/Schedule1Mods) (the `EmployeeReset` sidecar is the current shipped mod). Not for playing the game. user-invocable:
development
Pattern for an embedded HTTP control plane in a long-running process (game mod, simulator, GUI app, daemon) that exposes ALL runtime state plus the ability to drive ANY in-process operation. The first thing to build in a new project. It enables research, investigation, prototyping, and TDD. Use when starting any modding/embedding/long-running-process project, when adding observability or test surfaces to an existing one, or when answering "how do I see/poke this thing at runtime".