Every failure carries a code of the form tpl::<area>::<kind>.
Under --json it is the error.code field; in text output it is the first line of the diagnostic.
The codes are the stable surface.
Messages are not, and are expected to improve.
A caller that matches on prose will break the next time one does; a caller that matches on a code will not.
Removing or renaming a code is a breaking change, and a test pins the set so it cannot happen by accident.
$ gittpl--jsonrender./template--output./out--defaults
{"ok":false,"error":{ "code":"tpl::render::content", "message":"failed to render `Cargo.toml.jinja`", "causes":[{"code":"tpl::eval::expression","message":"undefined value","help":"..."}]}}
causes is where the actionable detail lives.
The outer error names the file; the one beneath it names the expression and the reason.
Branch on the outer code to decide what kind of failure it is, and read the innermost one to say why.
template.toml is not valid TOML, or a key it declares has the wrong type. An unknown key is ignored, not diagnosed, so a misspelled one reads as unset.
tpl::manifest::name_collision
A question and a computed value share a name.
tpl::manifest::invalid_question
A question declaration is not coherent — see the message. Covers a default_from naming no source, one whose expression does not parse, one referencing something that is not a seed namespace, and a default_when_skipped with no when to skip or no default to inject.
tpl::manifest::conflicting_note
Both note and note_file are declared. Keep one.
tpl::manifest::invalid_remote
A [remotes] entry has no name, no URL, or a name Git will not accept.
tpl::manifest::invalid_extends
An [extends] declaration has no source, no rev, or an empty remove path.
tpl::manifest::extends_kind_collision
A name is declared as a question by one layer of an [extends] chain and a computed value by another.
tpl::graph::invalid_expression
An expression in the manifest does not parse.
tpl::graph::unknown_reference
An expression names something the template never declares. Carries a suggestion.
tpl::graph::cycle
Questions, computed values or data sources depend on each other in a loop.
tpl::resolve::missing_root
The render root does not exist in the template.
tpl::resolve::dirty_needs_local
--dirty was used on a remote template, which has no working tree.
tpl::resolve::cache
The temporary clone could not be created.
tpl::extends::unpinned
An [extends].rev resolves to a branch rather than a tag or a commit. See ADR-034.
tpl::extends::cycle
An [extends] chain revisits a template it has already resolved.
tpl::extends::depth
An [extends] chain is deeper than the limit.
tpl::extends::remove_missing
[extends].remove names a path the parent does not have.
tpl::extends::untrusted
A remote [extends] ancestor was not authorised. Pass --trust, add the template to [trust], or answer the confirmation interactively — unlike tpl::data::untrusted, a git tpl test case's own trust field never grants this; see ADR-034.
tpl::extends::cancelled
The confirmation for a remote [extends] ancestor was cancelled.
A file failed to render. The cause names the expression.
tpl::render::path
A path segment failed to render.
tpl::render::escapes_tree
A piece of a rendered path is .., contains a backslash, or is the file's own name vanishing (empty or . in the last position). A segment may otherwise contain / — fanning out into real directories — or render . on a directory segment, which is transparent rather than an error.
tpl::render::collision
Two template files render to the same output path.
tpl::render::partial_not_utf8
A .jinja file outside the render root is not text.
Reported by git tpl lint as findings rather than raised as errors, so they arrive in the
diagnostics array rather than in error.
Only severity: "error" fails the command, unless --deny names a warning
or the whole severity.
Code
Severity
Meaning
tpl::lint::degenerate_path
error
A conditional segment leaves a literal suffix outside the block, so it renders to something like .yaml instead of being skipped.
tpl::lint::collision
error
Two paths can collapse to the same name for some answer set.
tpl::lint::syntax
error
A .jinja file does not parse, including in branches no answer set reaches.
tpl::lint::foreign_expression
warning
A ${{ ... }} MiniJinja will consume, rendering it to $.
tpl::lint::undeclared
warning
A file body uses a name the template does not declare. Renders empty unless strict = true.
tpl::lint::unguarded_gate
warning
A file body reads a when-gated question without checking is defined/is not defined or defaulting it. Absent, not null, whenever its when is false — unless default_when_skipped = true, which excludes the question from this rule.
tpl::lint::absorbed_key
warning
A top-level manifest key is written after a table header, so TOML gives it to that table. The top-level key is never set.
tpl::lint::shadowed_name
warning
An {% import %}/{% from %} alias, or a set/with/for/macro binding, reuses a question or computed name — the rest of the file sees the binding, not the answer, and a comparison against it is silently never true.
tpl::lint::shadowed_builtin
warning
A question or computed value is named after a MiniJinja global (range, dict, namespace, debug). Harmless while always present, but a when-gated question by that name is absent, not undefined, whenever its when is false — the builtin wins instead.
tpl::lint::missing_note_file
error
note_file names a path the template repository does not contain. Reported without a repository, before an init refuses.
tpl::lint::invalid_migration
error
A file in migrations/ is not valid TOML, or does not match the schema — see Migrations.
tpl::lint::missing_migration_file
error
A migration's message_file names a path the template repository does not contain.
These two are about the flags rather than the template, so they are raised as errors before anything is checked:
Code
Meaning
tpl::lint::unknown_code
A --deny or --allow names something that is neither warnings nor a code above.
tpl::lint::conflicting_level
The same code, or warnings, was both denied and allowed.
Raised by git tpl backport.
Every one of these is a refusal, never a wrong patch, and every one names editing the template by hand as the
fallback — which is the status quo, so a refusal never leaves you worse off than not having run the command.
The reasoning is ADR-020.
Code
Meaning
tpl::backport::substituted_region
A change lands on a line the template renders rather than copies, and its provenance is not exact enough to reverse — or the reversal was declined. The expected refusal. See Changing a line that holds a placeholder.
tpl::backport::round_trip
The patched template source did not render back to your file, so sending it would change what the template produces for everyone.
tpl::backport::binary
A changed file is binary, and a text patch cannot carry it.
tpl::backport::stale_rendering
The recorded answers no longer reproduce refs/tpl/<id>, so every line of the patch would be measured against the wrong file. Run update first.
tpl::backport::unknown_path
A named path is neither produced by the template nor present in the project.
tpl::backport::output_write
The patch could not be written to --output.
tpl::backport::hunk_refused
One of the hunks selected with -p or --hunk cannot be backported. The refusal underneath keeps its own code and its own advice; this names the hunk. Run again and leave that hunk out. See Choosing hunks.
tpl::backport::cancelled
The hunk picker was cancelled. No patch was produced and nothing was written.
tpl::backport::not_interactive
-p was asked for under --json, in a pipe, or with tpl.interactive false — where the hunks cannot be shown. Use --list-hunks and --hunk to choose hunks without a prompt, or limit the backport with pathspecs or --exclude.
tpl::backport::unknown_hunk
A --hunk is malformed, or names no hunk of the current change — usually because the file changed since --list-hunks. List the hunks again. See Naming hunks in advance.
Raised by git tpl test when the run cannot proceed.
A case that simply fails its expectations is not one of these: it arrives in the report's cases[].failures
array, and the command exits 1.
The area is testing rather than test, which is reserved for the diagnostic fixtures in src/report.rs.
Code
Meaning
tpl::testing::no_tests
The tests directory does not exist at the resolved revision, or holds no case files.
tpl::testing::no_such_case
A named case does not exist. Carries a suggestion and the available names.
tpl::testing::case_parse
A case file is not valid TOML, JSON or YAML.
tpl::testing::case_shape
A case file parses but is not a coherent case — an unknown key, a wrong type, contradictory expectations, or two files claiming one case name.
tpl::testing::remote_not_supported
test was pointed at a remote source; it only reads a local working tree, with or without --ref.
tpl::testing::snapshot_read
A recorded snapshot is unreadable, or its MANIFEST contradicts the files beside it.
tpl::testing::snapshot_write
A snapshot could not be written to the working tree.
tpl::testing::sandbox_failed
A case's temporary sandbox, for [commands] or an isolated git (ADR-033), could not be created.
tpl::testing::sandbox_write
The rendering could not be materialised into a case's sandbox.
tpl::testing::malformed_shard
--shard was not INDEX/TOTAL, both 1-based, INDEX <= TOTAL (ADR-036).
tpl::testing::empty_shard
--shard selected no cases at all — its TOTAL outgrew the suite.
tpl::testing::durations_read
The recorded durations file is unreadable, or contradicts itself.
tpl::testing::durations_write
The durations file could not be written to the working tree.