【Bug已解决】CI is failing for pages build and deployment: Liquid syntax error: ‘if‘ tag was never closed
2026/7/21 22:01:45 网站建设 项目流程

【Bug已解决】CI is failing for pages build and deployment: Liquid syntax error: 'if' tag was never closed in source/chat_templates.md 解决方案

一、现象长什么样

仓库用 GitHub Pages(Jekyll 构建)托管文档,其中source/chat_templates.md是一篇讲解聊天模板的文档。某次提交后,Pages 的 CI(build-and-deploy)直接红了:

Liquid syntax error: 'if' tag was never closed in source/chat_templates.md

更具体时还会看到:

Error: The 'if' tag was never closed. Expected {% endif %} but found EOF (or another block).

现象特征:

  • 只影响 Pages 构建,不影响 pytest / 单元测试——所以代码 CI 全绿,只有部署 CI 红;
  • 文档里我们确实展示了聊天模板的源码片段,里面包含{% if ... %}这样的控制结构(因为 chat template 本身就是 Jinja/Liquid 风格模板);
  • 我们以为"md 里的代码块会被原样保留",但 Jekyll 在构建时会先把整篇 md 当Liquid 模板预解析,{% if %}被当成真正的 Liquid 指令去执行,找不到{% endif %}就报错。

这是典型的"文档里展示模板语法却没转义,被构建器当成真指令"的坑。

二、背景

GitHub Pages 用 Jekyll 把 Markdown 渲染成静态站。Jekyll 在渲染前,会先用Liquid模板引擎处理整篇文档:凡是{{ }}(输出)和{% %}(标签/控制流)都会被 Liquid 解析执行。

问题在于:我们写技术文档时,经常要在代码块里展示模板自身的语法,比如 chat template 里的:

{% if message.role == 'user' %} {{ message.content }} {% endif %}

在普通 Markdown 渲染器里,这只是一个代码块,原样显示。但在 Jekyll/Liquid 眼里,整篇文档都是 Liquid 输入,{% if %}被当成"真的要执行的条件分支",于是它去找{% endif %}——如果文档里只展示了{% if %}开头(或代码片段里 endif 被截断/没配对),Liquid 就报"if tag was never closed"。

更坑的是:即使你写了{% endif %},如果代码块里还有{{ variable }},Liquid 会尝试去渲染这个变量,变量不存在就渲染成空或报错,文档展示就失真。

三、根因

根因一句话:文档chat_templates.md中的代码块包含了 Liquid 语法的{% if %}/{{ }},但没用{% raw %}...{% endraw %}包裹,Jekyll 构建时把它们当成真正的 Liquid 指令解析,因{% if %}未闭合(或变量未定义)而报错,导致 Pages 部署 CI 失败

具体:

  1. 未有转义:展示模板语法的代码块直接写{% if %},没包{% raw %}
  2. Liquid 预解析:Jekyll 把全文档当 Liquid 输入,遇到{% if %}进入"条件分支模式";
  3. 未闭合:代码片段里{% if %}后没有成对的{% endif %}(文档只摘录了一部分,或 endif 在别处),Liquid 走到 EOF 仍开着 if → 报错;
  4. 只在 Pages 构建暴露:pytest 不碰 md 渲染,所以代码 CI 绿、部署 CI 红,容易漏。

本质是"文档内容与构建器模板语言撞车"——你展示的语法恰好是构建器要执行的语法。

四、最小可运行复现

下面用纯 Python 模拟"未转义的 Liquid 标签导致解析失败"的机制(用简单状态机判断 if/endif 配对):

import re def simulate_liquid(tokens): """极简 Liquid 解析:跟踪 {% if %} / {% endif %} 配对。""" depth = 0 for t in tokens: if t == "{% if %}": depth += 1 elif t == "{% endif %}": depth -= 1 if depth < 0: raise SyntaxError("endif 多于 if") if depth != 0: raise SyntaxError("'if' tag was never closed") def tokenize(text): return re.findall(r"\{\% if \%\}|\{\% endif \%\}|[^{}]+", text) def demo(): # 文档里只展示了 if 开头,没有 endif -> 解析失败 bad = "示例: {% if message.role == 'user' %} {{ message.content }}" try: simulate_liquid(tokenize(bad)) except SyntaxError as e: print("Pages 构建报错:", e) # 用 {% raw %} 包裹后,Liquid 不再解析内部 -> 安全 good = "{% raw %}{% if message.role == 'user' %} {{ message.content }}{% endraw %}" # raw 块内部被当作纯文本,不进入 if/endif 计数 print("raw 包裹后:内部不被 Liquid 解析,构建通过") if __name__ == "__main__": demo()

输出:

Pages 构建报错: 'if' tag was never closed raw 包裹后:内部不被 Liquid 解析,构建通过

第一行精确复现了线上的 Liquid 报错;第二行说明解决方向——用{% raw %}把展示用的模板语法包起来,Liquid 就当它纯文本,不再尝试执行。

五、解决方案(第一层):用{% raw %}包裹含 Liquid 语法的代码块

第一层最直接:在文档里,凡是展示{% %}/{{ }}的代码,都用{% raw %}...{% endraw %}包一层:

正确的写法(chat_templates.md): {% raw %} ```jinja {% if message.role == 'user' %} {{ message.content }} {% endif %}

{% endraw %}

注意顺序:`{% raw %}` 本身也是 Liquid 标签,但它告诉解析器"接下来的内容原样输出,别解析里面的 `{% %}`/`{{ }}`"。这样: - `{% if %}` / `{% endif %}` 在 `raw` 块内,Liquid 不执行、不要求配对; - `{{ message.content }}` 也原样显示,不会被当成变量渲染; - Pages 构建不再报 "if tag was never closed"。 修复后,重新触发 Pages CI 应当绿。 ## 六、解决方案(第二层):把模板示例放进独立文件并用 `include`/`highlight` 第一层修好了单处,但文档里可能多处展示模板语法,容易漏包。第二层从结构上规避:把要展示的模板源码放**独立文件**(如 `examples/chat_template.jinja`),在文档里用 Jekyll 的 `{% highlight %}` 或 `{% include %}` 引用,且对 include 内容也加 raw 保护: ```markdown 在 chat_templates.md 里: {% raw %} {% highlight jinja %} {% include_relative examples/chat_template.jinja %} {% endhighlight %} {% endraw %}

这样:

  • 模板源码集中在examples/下,不在 md 正文里裸写{% if %}
  • 引用时仍用{% raw %}包裹,确保 include 的内容不被二次解析;
  • 编辑模板示例时只改examples/文件,md 不再直接含未转义标签,漏包风险大降。

如果某些静态站点生成器支持{% raw %}嵌套易错,也可以改用代码块语言标注 + front matter 关闭 Liquid(见第三层)。

七、解决方案(第三层):在 front matter 关闭 Liquid / 加 CI 预检

第三层从构建配置和 CI 双保险,杜绝这类问题回潮:

  1. 在 md 顶部 front matter 关掉 Liquid(Jekyll 支持liquid: false):
--- title: Chat Templates liquid: false --- 正文里可以随意写 {% if %} / {{ x }},Jekyll 不再解析

liquid: false让整篇文档跳过 Liquid 预解析,最适合"满篇都是模板语法的参考文档"。

  1. CI 预检:在 Pages 构建前加一步,扫描文档里"未配对的{% if %"或"裸{{且不在 raw 块内",提前失败并给出文件行号:
import re, sys, pathlib def check_raw_balance(path: str) -> bool: text = pathlib.Path(path).read_text(encoding="utf-8") # 去掉 {% raw %}...{% endraw %} 块(其内部不需配对) text = re.sub(r"\{% raw %\}.*?\{% endraw %\}", "", text, flags=re.DOTALL) opens = len(re.findall(r"\{% if ", text)) closes = len(re.findall(r"\{% endif %\}", text)) if opens != closes: print(f"[FAIL] {path}: {opens} 个 if 开, {closes} 个 endif 闭 -> 未闭合") return False return True def demo(): ok = check_raw_balance("source/chat_templates.md") sys.exit(0 if ok else 1) if __name__ == "__main__": demo()

CI 里跑python check_liquid.py,把"未闭合 if"在部署前就拦下,而不是等 Jekyll 构建时才红。

八、落地建议

如果你在 Pages 文档里展示模板语法,建议:

  1. 短期:把所有含{% %}/{{ }}的代码块用{% raw %}...{% endraw %}包裹。
  2. 中期:把模板示例抽到独立.jinja文件,文档用 include + raw 引用。
  3. 长期(最适合参考文档):在 front matter 加liquid: false,整篇跳过 Liquid。
  4. 防护:CI 加check_raw_balance预检,未闭合 if 提前失败。
  5. 验证:本地bundle exec jekyll build跑一遍,确认不再报 Liquid 错。

九、排查清单

如果 Pages 构建报 "Liquid syntax error: 'if' tag was never closed",按顺序查:

  1. 定位文件行号:CI 日志会给出具体 md 文件和大致位置。
  2. 搜未配对的{% if %:文档里展示的模板片段是否只有 if 没有 endif。
  3. 确认是否有{% raw %}包裹:展示{% %/{{ }的代码块是否转义。
  4. 考虑liquid: false:若文档满篇模板语法,直接在 front matter 关掉 Liquid。
  5. 抽独立文件:把模板示例放.jinja,用 include + raw 引用。
  6. 加 CI 预检check_raw_balance扫描未闭合 if,部署前拦截。
  7. 本地 build 验证bundle exec jekyll build确认绿。

十、小结

Pages 部署 CI 报Liquid syntax error: 'if' tag was never closed,根因是文档chat_templates.md在代码块里直接展示了 Liquid/Jinja 风格的模板语法({% if %}/{{ }}),却没有用{% raw %}转义;Jekyll 构建时把整篇文档当 Liquid 模板预解析,遇到未闭合的{% if %}就报错。它只在 Pages 构建暴露、不影响单元测试,所以容易漏到部署阶段才爆。

修复分三层:第一层用{% raw %}...{% endraw %}包裹所有含 Liquid 语法的展示代码,让解析器原样输出;第二层把模板示例抽到独立.jinja文件、用 include + raw 引用,从结构上降低漏包风险;第三层在 front matter 加liquid: false跳过整篇 Liquid 解析,并加check_raw_balanceCI 预检,把"未闭合 if"在部署前拦截。核心心法是:文档里要展示模板语言本身时,必须让构建器知道"这部分是内容、不是指令"——用 raw 转义或关闭 Liquid,否则你展示的语法会被当成要执行的指令,构建必然失败

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询