snbt-validation
Validate and lint FTB SNBT files (FTB Quests SNBT, FTB Library / Chunks / Ultimine / Teams configs, KubeJS data files, vanilla-SNBT typed arrays). Use when adding, editing, or reviewing any ``*.snbt``
SKILL.mdskills/snbt-validation/SKILL.md
GitHubSNBT Validation
Validate FTB Stringified NBT files against the grammar documented at https://docs.feed-the-beast.com/mod-docs/mods/technical/SNBT/. The validator lives at scripts/validate_snbt.py; the test suite at scripts/test_validate_snbt.py; the pre-commit hook at .githooks/pre-commit. None of this requires network access — everything runs offline against the working tree.
When to use
- Before committing any
*.snbtchange locally (the hook does this automatically; run the validator manually when the hook is bypassed with--no-verify). - When the pre-commit hook blocks a commit and reports one or more
file:line:coldiagnostics. - When authoring FTB Quests chapters, FTB Library/Chunks/Ultimine/Teams configs, or KubeJS data files.
- When porting SNBT from another tool (JEIT/NBT Studio, Amulet, vanilla
/data get) into FTB's dialect. - When CI emits an SNBT validator report and you need to map it back to a fix.
Quick start
# Validate every .snbt file under a directory (recursively):
python scripts/validate_snbt.py config/
# Validate explicit files:
python scripts/validate_snbt.py config/ftbchunks-client.snbt kubejs/data.snbt
# JSON output for CI / pipelines:
python scripts/validate_snbt.py --format json config/ > snbt-report.json
# Disable ANSI color when piping or logging:
python scripts/validate_snbt.py --no-color config/
Exit codes:
| Code | Meaning |
|---|---|
0 |
Every target validated cleanly. |
1 |
One or more diagnostics found. The validator prints each file:line:col-line:col issue with a code and human-readable message. |
2 |
Invocation error (missing path, unreadable file, bad flag). |
What it checks
The validator enforces the structural FTB SNBT grammar. It does not check semantics — a key being the right type for a particular mod's config schema is the mod's job.
#line comments (skipped at the lexer level).- Single- and double-quoted strings with
\\escapes; flags unterminated strings precisely. - All numeric forms:
<int>,<float>, hex0x..., with every FTB suffix (b/B/s/S/l/L/f/F/d/D); signed numbers (-128b,-1.5d); the special literals∞,-∞,∞F,-∞F,NaN,NanF. - Booleans
true/false(unquoted). Quoted"true"/"false"are strings, not booleans. - Unquoted identifier keys (and quoted
"with spaces"keys). {...}and[...]with FTB's defining rule: same-line multi-element values require,; values on different lines are separated by newlines alone.- Vanilla SNBT backward-compat typed-array syntax
[B; ...],[I; ...],[L; ...]. - Empty containers:
{},[],info_settings: { }. - UTF-8 BOM tolerance (FTB-generated configs sometimes include one).
- Duplicate keys inside the same compound (within the scope of a single object — nested objects are independent).
Reading diagnostics
Every diagnostic has the shape path:line:col[-end_line:end_col]: code: message. The code is stable and machine-greppable; the message is human-readable with the previous-token context that triggered the report.
| Code | Triggered by |
|---|---|
lex_error |
Invalid character, unterminated string, malformed number. |
expected_token |
A structural token (: after a key) is missing. |
missing_value |
A key's : is followed immediately by }, ,, or EOF. |
missing_comma |
Two values appear on the same source line with no , between them (FTB requires commas only on same-line sequences). |
unterminated_object / unterminated_array |
} or ] expected before EOF. |
unterminated_string |
Closing quote never found. |
invalid_value |
Bare identifier used as a value where a number / string / object was expected. |
invalid_key |
Object key position holds a punctuation token (commonly a stray }). |
duplicate_key |
Same key appears twice in the same compound. |
expected_comma_or_close |
The internal fallback when something genuinely unexpected appears after a value (e.g. a stray } mid-object). |
trailing_comma |
Array / object ends with a stray ,. |
typed_array_syntax |
[B/[I/[L seen without the required ; separator. |
typed_array_element |
[I; "not a number"] — wrong element type for the prefix. |
trailing_tokens |
Extra tokens after the top-level structure ended. |
empty_file |
File is empty. |
encoding_error / io_error |
File is not valid UTF-8 or cannot be read. |
When a fix is ambiguous (multiple ways to satisfy the grammar), the message includes the previous-item location so you can pick the right one.
Pre-commit integration
The hook at .githooks/pre-commit chains two checks for every commit:
packwiz refresh(existing — keepsindex.tomlhonest and re-stages it).python scripts/validate_snbt.py --no-coloron every staged*.snbtfile.
If the validator exits non-zero the commit is aborted; nothing is staged. The hook is not active by default — opt in with:
git config core.hooksPath .githooks
Run that once per clone. CI should mirror the same command (see the JSON format for greppable output).
Validator fallibility — read before trusting a pass
The validator is a structural grammar checker, not a game. It proves a file is well-formed SNBT; it cannot prove the file is correct for the game. Both false negatives (broken file passes) and false positives (working file flagged) exist. Known limits, each verified by probe:
False negatives — things it will PASS that are still wrong
- Semantics are entirely out of scope. A fully "valid" file can reference
nonexistent items (
totally_fake_mod:item), unknown quest fields (not_a_real_field: 42), broken dependencies, or malformed data-component codecs (e.g. an Iron's Spells scroll missingmaxSpells/index) and get a clean pass. Schema correctness is the mod's job; use the pack's other checks (packwizvalidation, in-game loading,docs/vvh/SPELL_SCROLL_FORMAT.md). - Formatting-code corruption is invisible.
title: "A & B"parses fine but breaks FTB Quests at render time (Invalid formatting! You must escape whitespace after & with \&!). The validator does not scan string contents. - Game-invalid values inside valid syntax.
level: 99on a capped spell, counts of 0, negative prices — all grammar-clean.
False positives — things it FLAGS that actually work in game
- Unquoted strings containing spaces are rejected (
invalid_value). Some FTB contexts tolerate them, but never rely on it: quote the string. Treat this flag as advice to fix your file, not a validator bug to work around. - Unquoted keys containing dots or leading digits are rejected (
lex_error/invalid_key). Quote such keys ("some.key": ...) if you genuinely need them. - Single-quoted strings cannot contain apostrophes —
'it's'reportsunterminated_string. FTB has no documented\'escape; reword or switch to double quotes.
Practical trust model
- A pass means: balanced structure, legal tokens, no duplicate keys. It does NOT mean the quest works.
- A flag usually means real breakage, but read the message — the unquoted- string cases above are strictness, not correctness.
- Grammar-clean is necessary, not sufficient. Always pair it with at least one
semantic check: in-game load of the chapter,
/createScroll-style runtime probes, or the scripted graph audits used in this repo.
Authoring tips
- When in doubt about grammar, copy a known-good file (
config/ftbchunks-client.snbtis a comprehensive real-world sample) and edit from there. - FTB's multi-line comma rule is the most common stumbling block. The validator reports
missing_commaonly when items share a source line; a fresh newline between entries is always valid. - Vanilla
[I; 1, 2, 3]is accepted; the FTB extension just adds multi-line support on top. - Trailing commas inside
[...]and{...}are rejected. If your editor adds them automatically, configure it not to. [B; ](empty typed array) is allowed and is in fact whatftblibrary-client.snbtuses to initialize an empty int-array — keep it that way.
Extending or testing
The validator ships with a self-contained test suite at scripts/test_validate_snbt.py. Run it with:
python scripts/test_validate_snbt.py
It walks both synthetic valid/invalid samples and every real *.snbt in the repository as an integration check. Add new tests when introducing a new diagnostic code or supporting a new SNBT construct; the existing ValidSamples / InvalidSamples split is the convention.