agentsclimarketplace

Jinja expert

Skill air-gapped/skills/.claude/skills/jinja-expert

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.From its SKILL.md

Install
npx -y skills add air-gapped/skills --skill jinja-expert

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 5 stars5 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.

SKILL.md

11.5 KB, ~2.6k tokens by cl100k_base, 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.

SignalDialectGo to
File named chat_template.jinja; apply_chat_template, tokenizer_config.json, vLLM/sglang in play; messages/tools/add_generation_prompt in scopeHF 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 expressionsAnsible 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 pluginHelm-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 functionsGo text/template + Sprig — not JinjaReach for the helm skill instead
Syntax looks like {{ }} but something else — .gotmpl, helmfile.yaml, Flask, Saltstack state, dbt model, Home Assistant configNot covered here; use the core-language cheatsheetreferences/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.

TaskGo to
Look up a statement, filter, test, or globalreferences/language-surface.md
Understand the exact env apply_chat_template buildsreferences/chat-template/transformers-dialect.md
Diagnose a specific category of chat-template bugreferences/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 fromreferences/chat-template/exemplars-and-antiexemplars.md
Debug a chat template or set up coverage/parity testingreferences/chat-template/debugging-and-testing.md
Write Ansible playbooks or .j2 files — filters, lookups, !unsafe, native types, k8s manifestsreferences/ansible-dialect.md
Jinja around Helm — values.yaml.j2, raw manifests, post-renderers, the Helm-is-Go disambiguationreferences/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.

#CategorySymptomDefensive idiom
1Content-format bifurcationTemplate 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.
2Loop-scope escape{% set flag = true %} inside {% for %} doesn't persist after the loop.{% set ns = namespace(flag=false) %} then {% set ns.flag = true %}.
3add_generation_prompt bimodalTemplate 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.
4Tool-call shape variancetool_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
5Whitespace controlStray \n before `<im_start
6Special-token doublingBOS 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 }}.
7Sandbox 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:

  1. when: takes a bare expression, not a Jinja template. when: "{{ foo }}" is always wrong; write when: foo. Same for failed_when:, changed_when:, until:.
  2. !unsafe is all-or-nothing. It disables templating for the whole value — including lookups. For mixed literal + templated content, use {% raw %}...{% endraw %}.
  3. 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.
  4. | 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:

  1. Ansible wraps Helm — values.yaml.j2 rendered by Ansible, installed via community.kubernetes.helm. Most common pattern.
  2. jinja2-cli preprocessor — same shape without Ansible. Makefile or CI step renders values.yaml.j2 → values.yaml, then helm upgrade --install.
  3. Raw Jinja manifests, no Helm — small apps or air-gapped sites. Ansible kubernetes.core.k8s with template: path/to.j2.
  4. Helm post-renderer chained with Jinja — niche; kustomize is usually the better fit here.
  5. kluctl — actually Jinja-native for Kubernetes GitOps. One of the few 2026 tools that isn't Go underneath.
  6. helm-jinja plugins — 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.

What ships with it: 11 files

135.5 KB alongside SKILL.md

evals/

Gives 0 of the 12 instructions most containers cloud skills give in ~2.6k tokens

Counted across 607 of the 705 authors here whose files we hold, read 2026-09-06

  • Run as non-root userin 34 of 607, across 27 files
  • Use multi-stage buildsin 29 of 607
  • Set resource requests and limitsin 24 of 607, across 20 files
  • Configure liveness and readiness probesin 18 of 607, across 14 files
  • Use named volumes for persistent datain 14 of 607, across 9 files
  • Pin base image versionsin 14 of 607
  • Set up environment variablesin 14 of 607, across 10 files
  • Pin provider versionsin 14 of 607
  • Apply least privilege RBAC permissionsin 10 of 607, across 7 files
  • Create a dockerignore filein 10 of 607
  • Use remote state with lockingin 9 of 607
  • Pin base images by digestin 9 of 607, across 8 files

Said here and by no other author read

  • Branch based on the dialect of the file under review
  • Use namespace for mutable variables inside loops
  • Explicitly check add_generation_prompt at the tail
  • Guard tool-call argument access against variance
  • Use trim_blocks and lstrip_blocks for whitespace control
  • Emit the BOS token in exactly one place

Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.

Keep looking

Skills are one crate of 623,721. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.