Repository navigation
ci(xeCJK): 3.10.5 的 CTAN announcement text 中有的 \ 变成了 ¥ #1066
Description
Activity
🐛 Bug 分析 #1066
项目 结果 根因 release-notes.md中的反斜杠是正确的,但 Claude 生成announcement.md后没有校验关键字符,导致部分 LaTeX 命令中的\被写成字面¥,随后原样上传到 CTAN。严重程度 🟢 low 适合后续自动实现 ✅ 是 📋 详细分析
复现路径:
- 运行
release-ctan-upload.yml的prepare-announcement,由scripts/extract-changes.py生成release-notes.md。 - Claude 按工作流提示翻译并写入
announcement.md。 - 工作流当前只检查文件非空,随后
l3build upload直接提交。 - CTAN 页面中可观察到
中文¥¥、¥hyper@anchor等字面内容。
代码定位:
- release-ctan-upload.yml:147-164:生成并展示唯一事实源
release-notes.md。 - release-ctan-upload.yml:194-257:LLM 翻译与写入
announcement.md,虽要求技术准确,但未约束或验证反斜杠字符。 - release-ctan-upload.yml:259-278:仅检查 announcement 非空并输出,没有字符完整性门禁。
- scripts/extract-changes.py:262-286:输出使用 UTF-8,未发现会把
\\转换为¥的逻辑。
问题原因:发布流水线把 LLM 输出当作可直接上传的最终文本;对 LaTeX 命令中的反斜杠没有做逐字校验,也没有比较源条目与译文中的命令记号。因而翻译模型的字符替换可进入 CTAN 说明。建议后续在上传前拒绝包含异常
¥的命令记号,或对源文本中的命令集合做一致性检查。
由 codex / gpt-5.6-sol 生成;本流程只分析,不自动修改代码。
- 运行
- addedgithub_actionsPull requests that update GitHub Actions codePull requests that update GitHub Actions code
on Aug 9, 2026 @muzimuzhi 目测和经典编码问题有关系。🤣
@muzimuzhi llm 生成的是正确的。可以确认是编码问题。不太确定出在 CTAN 那一侧还是 upload 步骤。之后查查看。
This release fixes several bugs related to CJK glue handling, brace-group consumption, hyperlink anchors, and underline/strikethrough decorations, and adds a new transparent wrapper variant for brace-delimited argument forwarding. BUG FIXES - \CJKecglue inserted at source-space positions now uses a portable code path, preventing its shrink component from being frozen inside the fixed-width segment boxes produced by ulem (#1037). - Before boundary detection, category-code-4 & in the parameter copy is replaced with \scan_stop:, fixing compilation errors when commands like \colorbox receive arguments containing & inside \halign (#1043). - \sbox now uses a dedicated adapter: prefixes such as \global must immediately precede \setbox, but assignments placed in the cmd/sbox/before hook consumed the prefix, causing \global\sbox to silently degrade to a local assignment (#1029). - Only the single triggering left brace is now absorbed instead of swallowing the entire group and re-emitting it, fixing the Improper alphabetic constant error when 中文\\ appears in tabular (#1038). - Inline anchor outputs \hyper@anchor, \Hy@raisedlink, and \__hyp_target_raise:n are registered as transparent captures so that \hypertarget, \phantomsection, \MakeLinkTarget, unnumbered heading anchors, and manually wrapped anchors no longer mask the actual boundaries on either side; the path via \hyper@anchorstart (e.g. `\pdfbookmark`) is not yet covered (#1047). - The stream capture for l3doc is moved from the internal \__codedoc_meta:n up to the public \meta, so that \CJKecglue on both sides is evaluated using the font metrics outside the command (#1046). - Period-style decorations now save end-of-segment state per nesting level, preventing an inner command from overwriting the right end of an outer one (#1012). - Body text now always enters ulem arguments as literal tokens: only "math followed by a trailing source space" is reformatted, and reformatting first assembles then expands once, preventing \CJKecglue to the right of Latin words from being trapped in a fixed-width decoration segment box and losing its shrink component (#1026). - The user manual now explains that a nested segment inside line-style commands cannot break across lines, distinguishes line-style from symbol-style commands, and provides alternative approaches; a regression guard for the \UL@onin path is added using "ability to line-break" as the observable (#1057). - Wavy underlines and diagonal strikethroughs now use a quarter-em drawing unit that scales with font size and ordinary \leaders; the minus-sign variant insets by half a period at each end to prevent adjacent commands from merging (#1012). - Decoration content is wrapped in an empty ActualText marker, and LaTeX tagging is suspended during decoration box construction, preventing glyphs used by wavy underlines and diagonal strikethroughs from appearing in PDF text extraction results (#1017). NEW FEATURES - A new transparent wrapper variant is added that forwards arguments delimited by braces, for use by target functions that consume their argument as \hbox:n content (#1047). Source code: https://github.com/CTeX-org/ctex-kit Issue tracker: https://github.com/CTeX-org/ctex-kit/issues@Liam0205 从日志看,也没啥问题。
https://github.com/CTeX-org/ctex-kit/actions/runs/31242377357/job/93065475564
那么大概率是 CTAN 自己的问题了。
- changed the title
[-]xeCJK: 3.10.5 的 CTAN announcement text 中有的 `\` 变成了 `¥`[/-][+]ci(xeCJK): 3.10.5 的 CTAN announcement text 中有的 `\` 变成了 `¥`[/+]on Aug 9, 2026 @Liam0205 给 CTAN 写封信 我觉得可能也需要同时抄送webmaster🫠
谢谢报告,问题属实。我把整条链路复现了一遍:我们投出去的内容是正确的,
¥由 CTAN 侧产生。不是我们这边的问题
逐环节查过:
1. 生成的
announcement.md正确。 workflow artifact 里第 9 行是`\hyper@anchor`、
`\Hy@raisedlink`,都是正常反斜线。2.
l3build的转义正确。l3build-upload.lua:329-332把\转义成\\写进 curl 的
config 文件(form-string="...")。我实测了 curl config 的还原规则:config 里的反斜线数 curl 实际发出 2 1 4 2 正好是 l3build 期待的语义,既没多转义也没少转义。
3. curl 实际发出的字节正确。 用同一份
announcement.md跑 l3build 的转义逻辑,再让
curl 把 config POST 到本地 socket 捕获请求体:--- CJKecglue --- '- `\CJKecglue` inserted at source-space positions' --- 中文 --- 'error when `中文\\` appears in `tabular` (#1038).' --- hyper@anchor ---'- Inline anchor outputs `\hyper@anchor`, `\Hy@raisedlink`,' 请求体里 U+00A5 (¥) 出现次数: 0 请求体里反斜线出现次数: 26一个
¥都没有,替换发生在 CTAN 接收之后。另外确认 CTAN 页面上存的是真的 U+00A5 字符,不是浏览器渲染问题:
中U+4E2D、
文U+6587、¥U+00A5、¥U+00A5。触发条件:CJK 与反斜线同行
¥这个字符是线索 —— JIS X 0201 把 0x5C 这个字节位定义为日元号而非反斜线。按行看 3.10.5 的损坏分布:
行 含 CJK 反斜线数 结果 5–7 否 1/3/5 正常 8 是 2 ¥9 起 否 8/3/1… 全部 ¥我一开始以为规律是「遇到 CJK 后切换编码判定,此后所有反斜线都坏」。但翻了 xeCJK 在
ctan-ann上的历史归档(共 63 条),3.10.2 那条否证了这个说法:它第 1 行就有 CJK
(em-dash (破折号)),第 21、22 行有\setlanguage、\verb、\meta共 6 个反斜线,
全部完好、0 个¥。两份样本的真正差别在这里:
- 3.10.2(未损坏):CJK 与反斜线从不在同一行;
- 3.10.5(损坏):第 8 行 CJK 与反斜线同行,损坏正是从这一行开始。
所以更贴合证据的假设是:当某一行同时含 CJK 字符和反斜线时触发,此后到文末的反斜线
都按¥处理。3.10.2 之所以没事,是因为它的 CJK 行恰好没有反斜线 —— 它其实没有测试到
这个条件。后半句「此后全坏」是已观察到的现象;至于是行级状态还是一次性切换后不再恢复,我没有更多
样本可以区分,标注为推测。关于「以前没出现」
补充一点背景:xeCJK 此前投递过 3.10.1/3.10.2/3.10.3/3.10.4,每次都带 announcement,
所以不是首次启用这条流程。真正的区别是这次的 announcement 里第一次出现了「同一行里
CJK 与反斜线共存」—— 这条讲的正是中文\\在tabular里报
Improper alphabetic constant(#1038),代码示例躲不开中文字符。接下来
我倾向报给 CTAN 团队,因为问题在他们的处理环节,而且不是 xeCJK 独有 —— 任何在
announcement 里让 CJK 与反斜线同行的包都会踩到,对中文 TeX 包是普遍问题。我们这边的规避办法是让两者不同行(例如把
中文\\那句改写,或者把 CJK 示例单独成行),
但那是替上游的 bug 买单,还会削弱说明力。所以想先报上去,看答复再定。如果你在
ctan-ann邮件列表上,能否顺便确认一件事:邮件原文(而非 web 归档页)里是
¥还是\?如果邮件正常、只有 web 归档坏了,问题范围会小很多,也更容易定位到具体是哪
一步做了这个替换。确认上游问题。
Reacted by Explorer如果你在
ctan-ann邮件列表上,能否顺便确认一件事:邮件原文(而非 web 归档页)里是
¥还是\?如果邮件正常、只有 web 归档坏了,问题范围会小很多,也更容易定位到具体是哪
一步做了这个替换。(也许已经知道/不需要以下信息了)
我收到的 ctan-ann 邮件原文和 ctan-ann archive 上,都是坏的。邮件原文
Content-Type字段包含charset="iso-2022-jp"From: CTAN Announcements via ctan-ann via ctan-ann <ctan-ann@ctan.org> Reply-To: ctan@ctan.org, CTAN Announcements via ctan-ann <erik@ctan.org> Content-Type: text/plain; format="flowed"; charset="iso-2022-jp" Content-Transfer-Encoding: 7bitCTAN 收到上传后,处理该上传的 CTAN 成员会给上传者发一封确认邮件(例如 pgf 3.1.12 的上传收到的),没准那时就已经编码错误了。
Reacted by Mingyu Hsia- 那会儿还是好的From my iPhone在 2026年8月11日,01:02,Yukai Chou ***@***.***> 写道:muzimuzhi left a comment (CTeX-org/ctex-kit#1066) 如果你在 ctan-ann 邮件列表上,能否顺便确认一件事:邮件原文(而非 web 归档页)里是 ¥ 还是 \?如果邮件正常、只有 web 归档坏了,问题范围会小很多,也更容易定位到具体是哪 一步做了这个替换。 (也许已经知道/不需要以下信息了) 我收到的 ctan-ann 邮件原文和 ctan-ann archive 上,都是坏的。邮件原文 Content-Type 字段包含 charset="iso-2022-jp" From: CTAN Announcements via ctan-ann via ctan-ann ***@***.***> Reply-To: ***@***.***, CTAN Announcements via ctan-ann ***@***.***> Content-Type: text/plain; format="flowed"; charset="iso-2022-jp" Content-Transfer-Encoding: 7bit CTAN 收到上传后,处理该上传的 CTAN 成员会给上传者发一封确认邮件(例如 pgf 3.1.12 的上传收到的),没准那时就已经编码错误了。 —Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you modified the open/close state.Message ID: ***@***.***>
xeCJK 的
CHANGELOG.md是中文的,发布到 CTAN 时则翻译为英文,且按修改类别分为 bug fixes、new features 等。xeCJK 3.10.5 的 CTAN announcement text,部分
\变成了¥。之前的 announcement text 无此问题。PS:我订阅了 ctan-ann.ctan.org mailing list,所以看到了 xecjk 3.10.5 的 announcement text。