Jinja expert
Claude Code plugin marketplace — 58 installable reference skills across vLLM/SGLang inference, Kubernetes & Harvester, GPU host bring-up, observability, security, and agent workflows.
npx -y skills add air-gapped/skills --skill jinja-expertAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Author, read, and debug Jinja2 templates across the three places Jinja lives in 2026 — HuggingFace `chat_template.jinja` (rendered by `apply_chat_template` for vLLM / sglang), Ansible playbooks + `.j2` files, and Jinja-adjacent Kubernetes workflows (`values.yaml.j2`, `kubernetes.core.k8s + template`, Helm post-renderers). Companion to the `helm` skill — Helm charts are Go `text/template` + Sprig, not Jinja, and this skill makes that disambiguation explicit.
SKILL.md
11.5 KB, as published. Nobody here has run it
Jinja Expert
Boundary with transformers-config-tokenizers-expert (same dev plugin).
Both skills touch chat_template.jinja, from opposite sides. This one owns the
template as Jinja — syntax, scoping, filters, whitespace, why the render is
wrong. That one owns which file wins and what the loader does with it:
tokenizer_config.json vs chat_template.jinja precedence, structural tokens,
which class actually instantiates. "My template renders the wrong text" is
here; "my template isn't the one being used" is there.
Jinja2 shows up in three mostly-disjoint ecosystems in 2026. Each has its
own dialect — different sandbox, different extensions, different filters,
different failure modes. Using chat-template idioms in an Ansible playbook
will silently misbehave. Using Ansible !unsafe in a chat template is a
syntax error. This skill is the map.
Which dialect am I in?
Pick the row that matches the file under review. The rightmost column points to the next file to read.
| Signal | Dialect | Go to |
|---|---|---|
File named chat_template.jinja; apply_chat_template, tokenizer_config.json, vLLM/sglang in play; messages/tools/add_generation_prompt in scope | HF chat template (sandboxed, loopcontrols, tojson ensure_ascii=False) | references/chat-template/ |
File ends in .j2; inside an Ansible playbook/role/collection; {{ ansible_managed }}, hostvars, lookup(...), ` | default(omit); when:` bare expressions | Ansible Jinja (recursive templating, native-types flag, !unsafe) |
values.yaml.j2 / deployment.yaml.j2 feeding Helm or kubectl; jinja2-cli; community.kubernetes.helm / kubernetes.core.k8s; ArgoCD config management plugin | Helm-adjacent Jinja (Jinja around Helm, not inside) | references/helm-and-jinja.md |
File inside a Helm chart (templates/*.yaml, _helpers.tpl, Chart.yaml); {{ .Values.x }}, {{ toYaml ... }}, {{ include "mychart.name" . }}, Sprig functions | Go text/template + Sprig — not Jinja | Reach for the helm skill instead |
Syntax looks like {{ }} but something else — .gotmpl, helmfile.yaml, Flask, Saltstack state, dbt model, Home Assistant config | Not covered here; use the core-language cheatsheet | references/language-surface.md + upstream docs |
When the dialect is uncertain, start at references/language-surface.md —
dialect-neutral, covers the statements, expressions, filters, tests,
globals, and whitespace rules shared by every Jinja deployment.
Quick decision guide (chat-template default)
Chat templates are the dialect most likely to bring someone here, so the body below stays biased toward them. When the task is Ansible or Helm-adjacent, branch to the matching reference and ignore the rest.
| Task | Go to |
|---|---|
| Look up a statement, filter, test, or global | references/language-surface.md |
Understand the exact env apply_chat_template builds | references/chat-template/transformers-dialect.md |
| Diagnose a specific category of chat-template bug | references/chat-template/seven-failure-categories.md |
| Write a new chat template (or patch an existing one) | references/chat-template/authoring-patterns.md |
| Find a "clean" chat template to study, or a broken one to learn from | references/chat-template/exemplars-and-antiexemplars.md |
| Debug a chat template or set up coverage/parity testing | references/chat-template/debugging-and-testing.md |
Write Ansible playbooks or .j2 files — filters, lookups, !unsafe, native types, k8s manifests | references/ansible-dialect.md |
Jinja around Helm — values.yaml.j2, raw manifests, post-renderers, the Helm-is-Go disambiguation | references/helm-and-jinja.md |
Author a Helm chart (Go templates, _helpers.tpl, Sprig) | cross to the helm skill |
The chat-template dialect at a glance
Every chat template in the HuggingFace ecosystem is rendered through an
ImmutableSandboxedEnvironment (trim_blocks=True, lstrip_blocks=True,
loopcontrols extension loaded, tojson filter forced to
ensure_ascii=False, raise_exception + strftime_now globals). vLLM
and sglang both delegate to tokenizer.apply_chat_template, so the
sandbox is the whole surface. .append / .pop / .update raise
SecurityError; ext.do / ext.i18n / ext.debug are NOT loaded.
Variables in scope: messages, tools, documents,
add_generation_prompt, all special_tokens_map entries, date_string,
plus caller chat_template_kwargs.
Full env config, sandbox rules, and canonical skeleton →
references/chat-template/transformers-dialect.md (env) and
references/chat-template/authoring-patterns.md (skeleton).
The seven chat-template failure categories
Roughly 90% of real chat-template bugs. Templates written against these defensive idioms survive the next model release.
| # | Category | Symptom | Defensive idiom |
|---|---|---|---|
| 1 | Content-format bifurcation | Template assumes content is a string; caller passes a list of parts. Python repr of the list lands in the prompt. | Branch is string → else is sequence and is not string → else is mapping → catch-all. Never isinstance. |
| 2 | Loop-scope escape | {% set flag = true %} inside {% for %} doesn't persist after the loop. | {% set ns = namespace(flag=false) %} then {% set ns.flag = true %}. |
| 3 | add_generation_prompt bimodal | Template hardcodes the assistant primer, breaking training; or reads the flag via **kwargs, so vLLM's AST detection can't see it. | Explicit {% if add_generation_prompt %} at the tail; render both modes in tests and diff. |
| 4 | Tool-call shape variance | tool_call.function.arguments is sometimes a JSON string, sometimes a dict. tool_call.id is optional. Unguarded access crashes. | `{{ args if args is string else args |
| 5 | Whitespace control | Stray \n before `< | im_start |
| 6 | Special-token doubling | BOS emitted by tokenizer (add_bos_token=True) AND by template ({{ bos_token }}) → double BOS. | Emit BOS in exactly one place. Check tokenizer_config.json before adding {{ bos_token }}. |
| 7 | Sandbox mutation attempts | .append, .pop, .update raise SecurityError. {% do x.append(...) %} doesn't parse (ext.do not loaded). | namespace() for accumulators; filter chains (selectattr, map, join) for transformations. |
Full examples, before/after code, canonical skeleton, and real-world
incidents → references/chat-template/seven-failure-categories.md +
references/chat-template/authoring-patterns.md.
The Ansible dialect at a glance
Ansible is the largest Jinja-in-production deployment outside web frameworks.
It adds a long list of things to stock Jinja — recursive templating, lazy
variable resolution, the !unsafe tag, native-types mode, hostvars,
conditional expressions without delimiters (when:, failed_when:,
changed_when:, until:), custom filter/test/lookup plugins, and a 22-level
variable precedence system. None of it lives in Pallets Jinja.
Four rules carry most of the distance:
when:takes a bare expression, not a Jinja template.when: "{{ foo }}"is always wrong; writewhen: foo. Same forfailed_when:,changed_when:,until:.!unsafeis all-or-nothing. It disables templating for the whole value — including lookups. For mixed literal + templated content, use{% raw %}...{% endraw %}.- Recursive templating amplifies errors. A variable whose value is itself Jinja-looking renders again. An undefined reference in round-two produces an error message that mentions neither round.
| default(omit)removes a task parameter entirely. The idiomatic way to make a parameter conditional without duplicating task definitions.
Everything else (filters, lookups, kubernetes.core.k8s + template,
custom plugins, ansible_managed, debugging with ansible-playbook --check --diff, j2lint, ten gotchas) → references/ansible-dialect.md.
Helm and Jinja — what really runs where
Helm charts do not use Jinja. They use Go's text/template + Sprig. Any
{{ ... }} inside a chart is Go syntax. The two languages look identical
for the first ten seconds and diverge hard after that — . semantics,
pipe argument shape (default 3 vs default(3)), Sprig's toYaml /
required / lookup, range rebinding. Chart authoring is owned by the
helm skill, not this one.
Where Jinja does show up in Kubernetes workflows:
- Ansible wraps Helm —
values.yaml.j2rendered by Ansible, installed viacommunity.kubernetes.helm. Most common pattern. jinja2-clipreprocessor — same shape without Ansible. Makefile or CI step rendersvalues.yaml.j2→values.yaml, thenhelm upgrade --install.- Raw Jinja manifests, no Helm — small apps or air-gapped sites.
Ansible
kubernetes.core.k8swithtemplate: path/to.j2. - Helm post-renderer chained with Jinja — niche; kustomize is usually the better fit here.
- kluctl — actually Jinja-native for Kubernetes GitOps. One of the few 2026 tools that isn't Go underneath.
helm-jinjaplugins — historically tried to swap Helm's engine; never thrived. Don't adopt in 2026. Use patterns 1/2/3 instead.
Full patterns, decision matrix, and the ten Go-vs-Jinja gotchas (default
arity, . scope, toYaml, required vs mandatory, lookup, CRD
ordering, genCA / genSignedCert, whitespace trim, double-templating)
→ references/helm-and-jinja.md.
Out of scope
Generic Flask web templating, custom ext.Extension authoring, async Jinja
(enable_async), bytecode caching, custom loaders, and Helm chart authoring
itself (Go text/template + Sprig — use the helm skill). For anything
in this list, the Pallets docs at https://jinja.palletsprojects.com/ are
authoritative for the core language.