没有不值得去解决的问题,也没有不值得去学习的技术!

每天批量处理 20 篇 Mixed WordPress 历史文章:Classic 转 Gutenberg、SyntaxHighlighter 迁移、摘要补全与英文覆盖翻译完整 SOP

图 2:复杂 Classic 一次转换成多个 Gutenberg 区块

作者:

WordPress AI 翻译工程实战

图1:SlyTranslate 使用 GLM-5.2 翻译文章时出现 Paragraph Run 错误

(1) SlyTranslate 翻译报错:修复 Paragraph Run 无法识别带属性的问题

WordPress 经典编辑器代码块无损迁移:批量转换 SyntaxHighlighter 短代码为 Gutenberg 区块

(2) WordPress 经典编辑器代码块无损迁移:批量转换 SyntaxHighlighter 短代码为 Gutenberg 区块

点击翻译按钮后,遇到一个弹窗“神秘”闪退问题及解决方法

(3) 点击翻译按钮后,遇到一个弹窗“神秘”闪退问题及解决方法

图5:DeepL API 注册页面,国家或地区列表中没有中国大陆

(4) 中国大陆 WordPress 博客想提升英文翻译质量,为什么这么麻烦?一次 Polylang + AutoPoly + DeepL API 排查复盘

图5:DeepSeek 的评分结果截图

(5) Chrome Built-in AI、Yandex Translate、ChatGPT Plus 翻译质量对比:我为什么决定重点英文文章改用 ChatGPT Plus

[截图 2:浏览器 Network 中直接请求 translate.yandex.net]

(6) 从 AutoPoly 到 Yandex 网页版:一次 WordPress 英文自动翻译质量验证

【图1:AutoPoly Free 与 Pro 功能对比截图】

(7) AutoPoly Pro + OpenAI API 能否自动化 WordPress 英文翻译?一次从技术到支付的可行性分析

【图2:点击“立即试用”后,ChatGPT 输入框中出现“代理模式”】

(8) ChatGPT Agent 能否替代 ChatGPT Plus 完成 WordPress 英文重译?

【图 7,SlyTranslate 显示翻译完成并创建文章 19426】

(9) SlyTranslate + 智谱 GLM-5.2 长文翻译排障:从 600 秒超时到完整生成英文 WordPress 文章

【图 5,54 个代码块精确匹配、差异数量为 0】

(10) WordPress SlyTranslate + GLM-5.2 长文单次翻译实战:排查 invalid_translation_runaway_output 假失败

【图 2,SlyTranslate 的 Additional Instructions 保持为空】

(11) SlyTranslate + GLM-5.2 翻译质量继续优化:从关闭随机采样、内置提示词到深度思考与模型 A/B 测试

【图6,终端中的完整测试结果】

(12) 在 GLM 5.2 之后测试 DeepSeek:一次面向 WordPress 长文翻译的模型对比与取舍

【图 4,“英文博客翻译质量优化”项目中已经整理完成的会话列表】

(13) 为英文博客翻译质量优化建立独立 ChatGPT 项目:整理历史会话并统一后续评测标准

【图3:Gemini 3.5 Flash 首次成功调用结果】

(14) 暂缓 OpenAI 后,我在本地测试了 Gemini:与 GLM 5.2 的 WordPress 技术文章翻译对比

图3:清理英文 Host 下的对象与术语关系缓存后,英文文章重新显示 SEO Tools、Yoast SEO 和 Blog SEO Log

(15) 为 SlyTranslate 的 GLM 5.2 整篇翻译补上 Plaintext 智能处理,并修复 Code Block Pro 区块失效

图5:英文文章发布后,前台特色图片、分类、标签和 Series 顺序均正常

(16) SlyTranslate + GLM-5.2 翻译 WordPress 长文章后,分类、标签、Series 与特色图片同步问题排查

图1:Codex 输出 Token 校验诊断结果

(17) SlyTranslate 全文翻译报错排查:完善 GLM 5.2 的段落与占位符约束

图1:创建 wordpress-ai-translation-pipeline 私有 GitHub 仓库

(18) 将 WordPress AI 翻译优化项目提交到 GitHub:一次阶段性的收尾

图1:文章列表中因摘要为空而重复出现两个 Post Views

(19) 从空摘要、历史格式混杂到重新翻译:我如何规划 WordPress AI 翻译工程的下一阶段

图4:最终批处理输出 42/42 完成、待处理 0、异常状态 0。

(20) 从只读审计到 42/42 完成:用 GLM 与 SlyTranslate 安全补全 WordPress 历史文章摘要

图3:GitHub 已正确识别仓库的 MIT License。

(21) 将 WordPress AI 翻译优化仓库从私有切换为公共:从敏感信息审计到最小改动发布

图1:仓库显示所有历史批次已经完成,并允许创建下一批。

(22) 每天批量处理 20 篇 WordPress 历史文章:代码块迁移、摘要补全与英文覆盖翻译完整 SOP

图 2:复杂 Classic 一次转换成多个 Gutenberg 区块

(23) 每天批量处理 20 篇 Mixed WordPress 历史文章:Classic 转 Gutenberg、SyntaxHighlighter 迁移、摘要补全与英文覆盖翻译完整 SOP

GLM-5.2 整篇翻译再次出现 Protected Token 校验失败:从尾部 Token 丢失到 Plaintext 结构重排的完整排查记录

(24) GLM-5.2 整篇翻译再次出现 Protected Token 校验失败:从尾部 Token 丢失到 Plaintext 结构重排的完整排查记录

图 1:历史文章 4652 在 GLM-5.2 整篇翻译时返回 HTTP 400 安全检测错误

(25) GLM-5.2 整篇翻译持续返回 HTTP 400:从安全检测拦截到 Plaintext 载荷缩减的完整排查与修复

图 1:历史文章批处理返回 HTTP 400,并提示内容安全检查未通过。

(26) GLM-5.2 整篇翻译返回 400:一次内容安全误拦截的逐步定位与恢复记录

图 5:文章最终已经 completed,但原来的 excerpt_generation_failed、HTTP 400 和人工完成记录同时保留。

(27) WordPress 历史文章自动化遇到 GLM 1301:从 HTTP 400 到 ChatGPT 人工兜底完成

图 1:index 814 的 INLINE Token 换序及中英文 Gutenberg 对照

(28) WordPress AI 翻译排错实战:一次合法的行内代码换序,为什么会阻断尾部结构自动修复?

图 1:完整文章首次整篇翻译失败,SlyTranslate 返回受保护标记校验错误

(29) WordPress 超长 Gutenberg 文章整篇 AI 翻译踩坑:从 1000+ 受保护标记到完整翻译成功

【图1:历史文章迁移最终状态,67 个固定批次、1281 篇文章全部完成】

(30) WordPress 历史文章迁移终于收尾:全量审计 1438 篇中文文章,并停用 SyntaxHighlighter Evolved

上一篇我整理了《每天批量处理 20 篇 WordPress 历史文章:代码块迁移、摘要补全与英文覆盖翻译完整 SOP》。

那一阶段主要处理已经属于 Gutenberg 格式、但正文中仍然存在 SyntaxHighlighter 的历史文章。继续清理以后,我发现站内仍然有不少中文历史文章包含 SyntaxHighlighter;进一步检查后确认,它们并不是此前漏扫,而是属于另一类历史格式:正文中同时存在 Gutenberg 区块、Classic / 经典编辑器内容和 SyntaxHighlighter,分析器会将其识别为 editor_format=mixed

此前 Gutenberg + SyntaxHighlighter 阶段只处理 editor_format=gutenberg 的文章,因此这些 Mixed 文章自然被留到了下一阶段。历史快照中共有 717 篇 Mixed 候选,其中 712 篇的主要问题是 editor-format-mixed;另外 5 篇还同时存在其他代码格式或结构异常,暂时单独保留。

因此第二阶段的目标很明确:先把 Classic / Mixed 内容规范化为 Gutenberg,再把 SyntaxHighlighter 迁移为 Code Block Pro,完成生产只读验证后,由仓库自动生成中文摘要并覆盖英文译文。


一、Mixed 阶段为什么要单独处理

Mixed 文章和前一阶段最大的区别,不是 SyntaxHighlighter 本身,而是整篇文章还没有真正成为规范 Gutenberg。即使后台已经能看到部分 Gutenberg 区块,也可能仍有 Classic 内容游离在区块结构之外。

因此 Mixed 阶段不能只做“SyntaxHighlighter → Code Block Pro”。在进入自动摘要和英文覆盖翻译以前,还必须同时满足:Classic / Mixed 内容已经完成 Gutenberg normalization、自动转换后的排版已经人工检查、SyntaxHighlighter 已归零、Code Block Pro 数量和语言正确,并且生产只读验证确认 editor_format=gutenbergclassic_outside_blocks=false

换句话说,“Classic 区块已经消失”并不能直接等价于“Gutenberg normalization 已经完成”。复杂 Classic 一键转换以后,仍然可能出现段落合并、标题变普通段落、编号错乱、换行消失、图片或 caption 位置变化等情况,这部分仍然需要人工检查。

二、两个仓库与人工职责

1. WordPress 翻译管线

仓库路径为 /home/wangqiang/code/wordpress-ai-translation-pipeline,主要负责 SlyTranslate 与 GLM 翻译定制、Gutenberg 结构保护、HTML / 代码 / 短代码保护、占位符校验以及英文整篇覆盖翻译。

2. 历史文章迁移仓库

仓库路径为 /home/wangqiang/code/wordpress-ai-excerpt-backfill,主要负责候选筛选、固定批次、人工转换状态、生产只读验证、中文摘要生成、英文覆盖翻译调度、执行证据、失败恢复与批次汇总。

3. 人工真正需要做什么

  • 打开仓库指定的文章,先判断当前正文是否已经是正常 Gutenberg;
  • 如果仍是 Classic、Mixed 或 unknown,再将正文规范化为 Gutenberg,并检查转换后的段落、标题、编号、列表和换行;
  • 如果仍存在 SyntaxHighlighter,则迁移为 Code Block Pro,并逐块核对语言;如果本来就没有旧代码结构,不为了流程额外修改正文;
  • 检查图片、caption、链接、正文顺序和是否存在损坏区块;
  • 确认当前文章已经满足生产验证要求,最后批量记录人工检查或转换已经完成。

正常批次中,人工不需要自己寻找下一批文章,也不需要手工生成中文摘要或逐篇点击英文覆盖翻译。只有自动模型因内容安全过滤等确定性原因无法完成、且统一恢复已经不能通过正常自动路径安全推进时,才进入后文的 ChatGPT 人工兜底流程。


三、完整状态流转

正常主流程仍然沿用上一阶段的状态机:

Plaintext
awaiting_manual_conversion
→ mark-converted
→ awaiting_readonly_validation
→ validate-live
→ ready_for_execution
→ run-ready --execute
→ completed

Mixed 阶段真正新增的是“整篇 Gutenberg normalization 已完成”的人工确认,以及生产验证对 editor_format=gutenbergclassic_outside_blocks=false 的检查。

到剩余空摘要文章阶段以后,这套状态机继续保持不变。变化的是候选入口已经放宽:文章不必先属于 Mixed 或包含 SyntaxHighlighter;但进入 ready_for_execution 以前,仍然必须通过原有 Gutenberg、Classic 残留、SyntaxHighlighter、代码结构和生产安全验证。

生产验证阶段仍然需要区分 awaiting_readonly_validationvalidation_failed;正式执行阶段的单篇失败则统一交给 recover。程序会根据本地状态、execution evidence、pre-write 基线和生产中文源自动选择安全恢复策略,后文单独整理。


四、每天开始前先检查仓库状态

每天开始操作以前,先进入仓库并检查全局状态:

Bash
cd ~/code/wordpress-ai-excerpt-backfill

python3 bin/history-migration.py status
python3 bin/history-migration.py summary

重点看“最新未完成批次”“下一步”“建议创建下一批”。如果仍然存在最新未完成批次,就继续完成当前批次,不要提前创建新的 20 篇。只有看到“最新未完成批次: 无”“建议创建下一批: True”“建议: all batches complete”时,才创建下一批。

五、创建新的 20 篇固定批次

2026 年 8 月 12 日:候选入口扩展到全部剩余空摘要文章

最初的 Mixed + SyntaxHighlighter 阶段使用 bin/build-mixed-syntaxhighlighter-batch.py 创建固定批次。到 2026 年 8 月 12 日,这一阶段以及最后 5 篇异常文章已经全部完成,历史迁移状态达到 45 个固定批次、857 篇文章全部 completed

随后重新只读盘点生产环境,确认仍有 424 篇中文已发布历史文章满足“中文摘要为空、存在 Polylang 英文对应文章、英文文章为 publish、尚未进入现有固定批次”。因此后续阶段不再以 Mixed、SyntaxHighlighter 或某一种代码格式作为候选入口,而是继续沿用现有批次生成器,只把候选条件放宽到全部剩余空摘要历史文章。

批次排序规则不变,仍然固定按 published_at DESCchinese_post_id DESC 排序,也就是从发布时间较新的历史文章向较旧文章推进;每批最多 20 篇,最后不足 20 篇时直接处理全部剩余候选。

脚本和批次名称目前仍然保留 mixed-syntaxhighlighter,这是为了继续兼容已经稳定运行的固定批次、状态目录和 history-migration 工作流,并不表示新阶段的候选仍然必须包含 SyntaxHighlighter。

确认上一批已经完成以后,可以执行:

Bash
cd ~/code/wordpress-ai-excerpt-backfill

summary_output="$(
    python3 bin/history-migration.py summary
)"

printf '%s\n' "$summary_output"

if ! grep -q '^建议创建下一批: True$' <<<"$summary_output"; then
    echo
    echo "当前不允许创建下一批,请先完成最新未完成批次。"
else
    date_tag="$(date +%Y%m%d)"
    batch_id=""
    batch_file=""

    for number in $(seq -w 1 99)
    do
        candidate_id="mixed-syntaxhighlighter-${date_tag}-${number}"
        candidate_file="data/analysis/mixed-syntaxhighlighter-migration-batch-${date_tag}-${number}.csv"
        candidate_state_dir="data/state/history-migration/${candidate_id}"

        if [ ! -e "$candidate_file" ] && [ ! -d "$candidate_state_dir" ]; then
            batch_id="$candidate_id"
            batch_file="$candidate_file"
            break
        fi
    done

    if [ -z "$batch_id" ]; then
        echo "无法生成可用的新批次编号。"
    else
        raw_file="data/raw/history-general-candidates-20260812-01.jsonl"
        preview_file="data/analysis/history-general-candidates-20260812-01-preview.csv"
        translations_file="data/raw/history-general-candidates-20260812-01-translations.jsonl"

        if [ ! -f "$raw_file" ]; then
            echo "找不到原始候选快照:$raw_file"
        elif [ ! -f "$preview_file" ]; then
            echo "找不到 preview:$preview_file"
        elif [ ! -f "$translations_file" ]; then
            echo "找不到翻译关系:$translations_file"
        else
            echo
            echo "新批次 ID:$batch_id"
            echo "新批次文件:$batch_file"

            if python3 bin/build-mixed-syntaxhighlighter-batch.py \
                --preview "$preview_file" \
                --translations "$translations_file" \
                --output "$batch_file" \
                --batch-id "$batch_id" \
                --maximum 20 \
                "$raw_file"
            then
                if [ -s "$batch_file" ] && [ "$(wc -l < "$batch_file")" -gt 1 ]; then
                    python3 bin/history-migration.py init-state --apply \
                    && echo \
                    && echo "新批次已经创建:" \
                    && echo "batch_id=\"$batch_id\"" \
                    && echo "csv_file=\"$batch_file\""
                else
                    echo
                    echo "没有创建新批次。"
                    echo "请检查上面的 selected_count 和 remaining_eligible_count。"
                fi
            else
                echo
                echo "创建新批次失败,未执行 init-state。"
            fi
        fi
    fi
fi

这里仍然先检查 summary 是否允许创建下一批;只有批次 CSV 确实生成且至少包含一条候选记录时,才执行 init-state --apply 并输出“新批次已经创建”。这样可以避免候选池已经耗尽时仍然误报创建成功。

如果最后剩余候选不足 20 篇,则直接处理全部剩余候选。

六、设置批次变量并输出后台编辑地址

每天只需要单独设置两个会变化的变量,例如:

Bash
batch_id="mixed-syntaxhighlighter-YYYYMMDD-01"
csv_file="data/analysis/mixed-syntaxhighlighter-migration-batch-YYYYMMDD-01.csv"

然后一次性输出当前批次仍未完成文章的后台编辑地址:

Bash
python3 - "$batch_id" "$csv_file" <<'PY'
import csv
import json
import sys
from pathlib import Path

batch_id = sys.argv[1]
csv_path = Path(sys.argv[2])
state_dir = Path("data/state/history-migration") / batch_id

with csv_path.open(encoding="utf-8-sig", newline="") as f:
    rows = list(csv.DictReader(f))

items = []

for row in rows:
    post_id = int(row["chinese_post_id"])
    state_path = state_dir / f"chinese-{post_id}.json"

    state = {}
    if state_path.is_file():
        state = json.loads(state_path.read_text(encoding="utf-8"))

    status = state.get("workflow_status", "uninitialized")

    if status == "completed":
        continue

    edit_url = (
        "https://admin.shuijingwanwq.com/wp-admin/"
        f"post.php?post={post_id}&action=edit"
    )

    items.append({
        "post_id": post_id,
        "english_id": row["english_post_id"],
        "title": row["chinese_title"],
        "syntax": row["before_syntaxhighlighter_count"],
        "status": status,
        "edit_url": edit_url,
    })

print(f"批次:{batch_id}")
print(f"当前待处理:{len(items)}")
print()

for index, item in enumerate(items, 1):
    print(
        f"{index:02d}. "
        f"zh={item['post_id']} "
        f"en={item['english_id']} "
        f"SH={item['syntax']} "
        f"status={item['status']}"
    )
    print(f"    标题:{item['title']}")
    print(f"    编辑:{item['edit_url']}")
    print()
PY

七、人工检查并规范化为 Gutenberg

1. 先判断文章是否真的需要修改

进入剩余空摘要文章阶段以后,并不是每篇文章都必须修改正文。2026 年 8 月 12 日的只读盘点显示,424 篇正式候选中有 74 篇已经是正常 Gutenberg,并且没有需要迁移的旧代码格式;这类文章只需要人工确认结构和排版正常,不需要为了迁移流程再次保存或改写正文。

2. Classic / Mixed / unknown 规范化为 Gutenberg

如果文章仍然属于 Classic、Mixed 或 unknown,则继续按照此前规则规范化为 Gutenberg。简单 Classic 内容可以直接使用 WordPress 的“转换为区块”;复杂内容转换以后仍然必须人工检查排版。

保存以前至少确认:段落、标题、编号、列表、换行、图片、caption、链接和正文顺序正常;没有内容遗漏、重复或损坏区块。最终必须能够通过现有生产验证对 editor_format=gutenbergclassic_outside_blocks=false 等条件的检查。

3. 旧代码结构仍按现有规则处理

如果发现旧代码结构,则继续按照现有验证规则处理。SyntaxHighlighter 最终必须为 0;如果仍然存在 SyntaxHighlighter,则迁移为 Code Block Pro,并逐块核对语言。原区块已经声明语言时继续使用对应语言;没有声明语言时优先使用 Plaintext,不要让 Code Block Pro 无意继承上一个代码块的语言。

现有验证器禁止的 core/code、classic pre/code 或其他异常结构,也必须在进入生产只读验证以前完成规范化。当前剩余 424 篇候选的只读盘点中,SyntaxHighlighter 数量已经为 0;保留这项硬性验证,是为了确保全部历史文章处理结束以后可以安全删除 SyntaxHighlighter 插件。

这里不要手工生成摘要,也不要修改英文译文。

八、批量记录人工转换完成

全部文章完成必要的人工检查或转换以后,需要把状态从 awaiting_manual_conversion 推进到 awaiting_readonly_validation。对于本来已经是干净 Gutenberg 的文章,这里的确认表示已经人工检查,不代表正文必须发生修改。先确认 $batch_id$csv_file,再执行:

Bash
read -r -p \
  "确认当前批次文章均已完成 Gutenberg 状态检查;需要转换的文章已完成规范化,SyntaxHighlighter 已归零,如存在 Code Block Pro 也已完成语言核对。输入 YES 继续:" \
  answer

if [ "$answer" != "YES" ]; then
    echo "未确认,已停止,没有修改状态。"
else
    python3 - "$batch_id" "$csv_file" <<'PY'
import csv
import json
import subprocess
import sys
from pathlib import Path

batch_id = sys.argv[1]
csv_path = Path(sys.argv[2])
state_dir = Path("data/state/history-migration") / batch_id

with csv_path.open(encoding="utf-8-sig", newline="") as f:
    rows = list(csv.DictReader(f))

def status_of(post_id):
    path = state_dir / f"chinese-{post_id}.json"
    if not path.is_file():
        raise RuntimeError(f"缺少状态文件:{path}")
    return json.loads(path.read_text(encoding="utf-8"))["workflow_status"]

targets = [
    row for row in rows
    if status_of(int(row["chinese_post_id"]))
    == "awaiting_manual_conversion"
]

print(f"批次:{batch_id}")
print(f"等待记录人工转换:{len(targets)}")
print()

success = []
failed = []

for index, row in enumerate(targets, 1):
    post_id = row["chinese_post_id"]
    syntax_before = row["before_syntaxhighlighter_count"]
    cbp_after = row["expected_code_block_pro_count_after"]

    result = subprocess.run(
        [
            sys.executable,
            "bin/history-migration.py",
            "mark-converted",
            "--post-id", post_id,
            "--syntax-count-before", syntax_before,
            "--cbp-count-after", cbp_after,
            "--language-review-confirmed",
            "--gutenberg-normalization-confirmed",
        ],
        text=True,
        capture_output=True,
        check=False,
    )

    final_status = status_of(int(post_id))

    if result.returncode == 0 and final_status == "awaiting_readonly_validation":
        success.append(post_id)
        print(f"[{index}/{len(targets)}] 已记录:zh={post_id}")
    else:
        failed.append(post_id)
        output = (result.stderr or result.stdout or "").strip().splitlines()
        error = output[-1] if output else "没有错误摘要"
        print(
            f"[{index}/{len(targets)}] "
            f"记录失败:zh={post_id} "
            f"error={error}"
        )

print()
print("========== 人工转换记录汇总 ==========")
print(f"待记录:{len(targets)}")
print(f"成功:{len(success)}")
print(f"失败:{len(failed)}")

if failed:
    print("失败文章:" + ", ".join(failed))
    raise SystemExit(1)
PY
fi

正常情况下,汇总应显示本批待记录文章全部成功,失败为 0。


九、执行生产只读验证

1. 从浏览器获取 Cookie 和 REST Nonce

生产只读验证需要使用当前已经登录 WordPress 后台的会话。WP_ADMIN_COOKIE 保存后台请求中的完整 Cookie 值,WP_REST_NONCE 保存同一登录会话中的 X-WP-Nonce 值。

可以在浏览器中按下面的方式获取:

  • 登录当前使用的 WordPress 管理后台,并打开任意文章编辑页面;
  • 打开浏览器开发者工具,切换到“网络 / Network”;
  • 刷新编辑页面,或者执行一次不会破坏内容的后台操作;
  • 在网络请求中筛选 wp-json,打开一个状态为 200 的 WordPress REST API 请求;
  • 在请求头中复制 Cookie 的完整值,作为 WP_ADMIN_COOKIE
  • 在同一请求的请求头中复制 X-WP-Nonce 的值,作为 WP_REST_NONCE

复制时只取请求头冒号后面的值,不要把 Cookie:X-WP-Nonce: 这两个请求头名称一起复制。两项内容最好来自同一个成功请求,这样可以避免 Cookie 和 Nonce 分别属于不同登录会话。

Cookie 和 REST Nonce 都属于敏感认证信息,不应写入博客、截图、Git 仓库、脚本源码或提交记录。

2. 在当前终端设置环境变量

不要直接执行带有真实值的 export WP_ADMIN_COOKIE='...',否则内容可能进入 shell 历史。可以使用静默输入;粘贴时终端不会显示实际内容:

Bash
if [ -z "${WP_ADMIN_COOKIE-}" ]; then
    read -r -s -p "请输入新的 WordPress Cookie:" WP_ADMIN_COOKIE
    echo
    export WP_ADMIN_COOKIE
fi

if [ -z "${WP_REST_NONCE-}" ]; then
    read -r -s -p "请输入新的 X-WP-Nonce:" WP_REST_NONCE
    echo
    export WP_REST_NONCE
fi

这里的 export 只会让变量在当前 shell / 终端会话及其子进程中生效。关闭终端、重新打开新的 VS Code Terminal,或者换到另一个终端标签页以后,通常都需要重新设置。当前流程没有把这些敏感值写入 .env~/.bashrc 或其他持久化文件。

3. 检查变量,并识别已经过期的认证信息

设置后可以只检查变量是否非空,不打印真实内容:

Bash
for name in WP_ADMIN_COOKIE WP_REST_NONCE
do
    if [ -z "${!name-}" ]; then
        echo "缺少环境变量:$name"
    else
        echo "已设置:$name"
    fi
done

需要特别注意:这里的“已设置”只代表变量非空,并不代表 Cookie 或 REST Nonce 仍然有效。2026 年 7 月 31 日的实际执行中,我就遇到过 WP_REST_NONCE 已存在、但实际请求连续返回 HTTP 403 的情况;重新获取并设置新的 Nonce 后,同一批次立即恢复正常。

因此,如果多篇文章都在同一个 REST 预检位置连续返回 403,不应该优先怀疑文章内容,而应该先检查当前认证信息,尤其是 WP_REST_NONCE。需要更新时可以直接重新静默输入并覆盖旧值:

Bash
read -r -s -p "请输入新的 WordPress Cookie:" WP_ADMIN_COOKIE
echo
export WP_ADMIN_COOKIE

read -r -s -p "请输入新的 X-WP-Nonce:" WP_REST_NONCE
echo
export WP_REST_NONCE

如果已经确认 Cookie 仍然有效,也可以只重新设置 WP_REST_NONCE;无法确定时,最好从同一个新的成功 REST 请求中重新复制两项值。

4. 批量生产只读验证,并对偶发 SSH 超时有限重试

进入剩余空摘要文章阶段以后,生产只读验证继续沿用原有结构和生产安全检查。正式执行以前,当前中文正文必须已经成为正常 Gutenberg,classic_outside_blocks=falseSyntaxHighlighter=0,并通过现有 Code Block Pro、损坏区块、Polylang、发布状态、SHA-256 和 target drift 等检查。候选入口虽然已经放宽,但执行验证标准并没有放宽。实际运行中如果出现 batch read-only SSH query timed out 或 SSH exit 255,而状态仍然停留在 awaiting_readonly_validation,通常属于只读查询没有成功完成,可以有限重试。

Bash
missing=0

for name in WP_ADMIN_COOKIE WP_REST_NONCE
do
    if [ -z "${!name-}" ]; then
        echo "缺少环境变量:$name"
        missing=1
    fi
done

if [ "$missing" -ne 0 ]; then
    echo "已停止,未执行生产只读验证。"
else
    python3 - "$batch_id" "$csv_file" <<'PY'
import csv
import json
import subprocess
import sys
import time
from pathlib import Path

batch_id = sys.argv[1]
csv_path = Path(sys.argv[2])
state_dir = Path("data/state/history-migration") / batch_id

with csv_path.open(encoding="utf-8-sig", newline="") as f:
    rows = list(csv.DictReader(f))

def status_of(post_id):
    path = state_dir / f"chinese-{post_id}.json"
    if not path.is_file():
        raise RuntimeError(f"缺少状态文件:{path}")
    return json.loads(
        path.read_text(encoding="utf-8")
    )["workflow_status"]

targets = []
unexpected = []

for row in rows:
    post_id = int(row["chinese_post_id"])
    status = status_of(post_id)

    if status == "awaiting_readonly_validation":
        targets.append(post_id)
    elif status not in ("ready_for_execution", "completed"):
        unexpected.append((post_id, status))

print(f"批次:{batch_id}")
print(f"本次待验证:{len(targets)}")
print()

success = []
failed = []

for index, post_id in enumerate(targets, 1):
    passed = False

    for attempt in range(1, 4):
        result = subprocess.run(
            [
                sys.executable,
                "bin/history-migration.py",
                "validate-live",
                "--post-id",
                str(post_id),
            ],
            text=True,
            capture_output=True,
            check=False,
        )

        final_status = status_of(post_id)

        if result.returncode == 0 and final_status == "ready_for_execution":
            success.append(post_id)
            if attempt == 1:
                print(f"[{index}/{len(targets)}] 验证通过:zh={post_id}")
            else:
                print(
                    f"[{index}/{len(targets)}] "
                    f"重试通过:zh={post_id} attempts={attempt}"
                )
            passed = True
            break

        output = (result.stderr or result.stdout or "").strip().splitlines()
        error = output[-1] if output else "没有错误摘要"

        print(
            f"[{index}/{len(targets)}] "
            f"第 {attempt}/3 次失败:zh={post_id} "
            f"status={final_status} "
            f"error={error}"
        )

        if final_status != "awaiting_readonly_validation":
            break

        if attempt < 3:
            time.sleep(5)

    if not passed:
        failed.append(post_id)

print()
print("========== 只读验证最终汇总 ==========")
print(f"本次待验证:{len(targets)}")
print(f"本次通过:{len(success)}")
print(f"本次失败:{len(failed)}")

if unexpected:
    print(
        "非预期状态:"
        + ", ".join(
            f"{post_id}:{status}"
            for post_id, status in unexpected
        )
    )

if failed:
    print("验证失败文章:" + ", ".join(map(str, failed)))

if failed or unexpected:
    raise SystemExit(1)
PY

    result=$?

    echo

    if [ "$result" -eq 0 ]; then
        echo "========== 验证后的待执行文章 =========="
        python3 bin/history-migration.py run-ready \
            --batch-id "$batch_id"
    else
        echo "存在验证失败或非预期状态,暂不进入正式执行。"
    fi
fi

如果文章真正进入 validation_failed,就不要再把它当成网络偶发超时。应先修复中文文章,再使用 validate-live --post-id 文章ID --refresh 重新验证。


十、正式生成中文摘要并覆盖英文译文

1. 设置智谱 API Key

生产只读验证只需要 WP_ADMIN_COOKIEWP_REST_NONCE。正式生成中文摘要时还需要项目现有的智谱 API Key,也就是 ZHIPU_API_KEY

同样不要把真实 API Key 直接写进命令历史。可以在当前终端中静默输入:

Bash
if [ -z "${ZHIPU_API_KEY-}" ]; then
    read -r -s -p "请输入智谱 API Key:" ZHIPU_API_KEY
    echo
    export ZHIPU_API_KEY
fi

ZHIPU_API_KEY 也只在当前终端会话中生效,不应写入博客、截图、Git 仓库或公开配置文件。

正式执行前统一检查三项变量:

Bash
missing=0

for name in WP_ADMIN_COOKIE WP_REST_NONCE ZHIPU_API_KEY
do
    if [ -z "${!name-}" ]; then
        echo "缺少环境变量:$name"
        missing=1
    else
        echo "已设置:$name"
    fi
done

if [ "$missing" -ne 0 ]; then
    echo "环境变量不完整,暂不执行正式批次。"
fi

这项检查仍然只验证变量是否非空。Cookie、REST Nonce 是否有效,要以实际 REST 预检结果为准;如果多篇文章同时返回 403,应先更新认证信息,而不是继续消耗单篇重试次数。

2. 正式执行批次

确认三项变量均已设置,而且当前认证信息有效后执行:

Bash
python3 bin/history-migration.py run-ready \
    --batch-id "$batch_id" \
    --execute

程序会逐篇执行中文摘要生成、写入校验和英文覆盖翻译。真正的超时、DNS、连接中断和短暂服务不可用仍由有限重试机制处理,因此看到一次暂时性网络错误时不必立即中止整个批次。

但 HTTP 500 不能再一律视为可重试的服务端波动。程序现在会解析 WordPress 返回的 JSON codemessage:例如 swq_full_article_token_validation_failed 会被归类为 protected_token_validation_error,第一次失败后立即停止。认证类 403 也要区别看待;多篇文章在同一 REST 预检位置连续返回 403 时,应先更新 WP_ADMIN_COOKIEWP_REST_NONCE,而不是继续消耗单篇重试次数。

十一、批次结束后如何判断真正完成

正式执行结束后,再执行:

Bash
python3 bin/history-migration.py run-ready \
    --batch-id "$batch_id"

python3 bin/history-migration.py status
python3 bin/history-migration.py summary

真正完成不能只看 selected_count=0。它只表示当前没有可由 run-ready 直接执行的文章,并不代表不存在 translation_failedvalidation_failedblocked

最终应同时满足:当前批次 completed=批次总数remaining=0、没有失败状态、integrity=ok,并且全局输出显示“最新未完成批次: 无”“建议创建下一批: True”。


十二、异常状态与错误分类

从 2026 年 8 月 4 日开始,异常恢复不再要求人工先判断应该使用 resume 还是 restart-from-current。但在进入统一恢复命令以前,仍然需要先区分“验证阶段失败”和“正式执行阶段失败”,因为两者处理的证据与安全边界不同。

1. awaiting_readonly_validation + SSH timeout

如果生产只读查询报 batch read-only SSH query timed out,并且文章状态仍然是 awaiting_readonly_validation,说明验证尚未形成成功或失败结论,可以对仍处于该状态的文章有限重试。

SSH 退出码 255 现在会在底层最多尝试三次并短暂退避。三次均失败时,应报告 production_readonly_unavailable 和“内容变化未知”,期间不得写入状态,也不能把 changed=False 误当成“中文源没有变化”。

2. validation_failed

这表示生产只读验证已经形成明确结论,不属于网络偶发错误。应先修复 WordPress 中文文章,再执行:

Bash
python3 bin/history-migration.py validate-live \
    --post-id 文章ID \
    --refresh

只有重新验证通过并进入 ready_for_execution 后,才继续正式执行。

3. 正式执行失败要区分暂时性与确定性错误

正式执行阶段仍可能出现真正的暂时性错误,例如 GLM 请求超时、DNS 失败、连接中断或短暂服务不可用。这类错误继续使用有限重试,批次不会因为单篇偶发失败而立即中止。

但 HTTP 500 本身不能再直接等同于网络故障。程序现在会继续解析 WordPress 返回的 JSON codemessagedata。例如 swq_full_article_token_validation_failed 会被分类为 protected_token_validation_error,属于确定性的内容或保护标记校验错误,第一次失败后就停止,不再无意义地自动重试三次。

批次输出和状态文件会保留真实错误类别、WordPress 错误信息以及 execution evidence 路径。遇到这类错误时,应先查看错误信息并修复对应中文源,再使用后文的统一 recover 命令。

4. 摘要阶段 HTTP 400 / 1301 内容安全过滤

2026 年 8 月 14 日又补充了一类不能盲目重试的摘要阶段错误:智谱接口返回 HTTP 400,但真实响应中包含 error.code=1301,并提示输入或生成内容可能包含不安全或敏感内容。这类失败不是普通网络波动,也不能只根据 HTTP 400 猜测为请求长度或参数问题。

现在 HTTP JSON 层会同时识别顶层和嵌套的 error.code / error.message,execution evidence 也会保留脱敏后的 HTTP status、错误响应以及请求尺寸统计。需要先看真实 evidence:如果已经明确是 1301 内容安全过滤,就停止无意义的重复请求;如果最新一次只是 TimeoutError,则仍应把它与原始内容过滤根因分开记录,不能互相覆盖。

对于这种确定性的模型拒绝,如果文章本身没有需要修改的内容,不应为了迎合某一个模型而改写历史文章。后文新增了 ChatGPT 人工兜底流程,用外部人工完成中文摘要和英文译文,再通过只读验收和 mark-manual-completed 收敛到 completed

5. 多篇同时出现 403

如果多篇文章在相同 REST 预检位置连续返回 403,应优先检查 WP_ADMIN_COOKIEWP_REST_NONCE。这属于公共认证依赖失效,不是多篇文章同时出现内容异常,也不应继续消耗单篇重试次数。


十三、统一使用 recover 恢复单篇失败文章

此前恢复流程分散在 resumerestart-from-current --applyrun-ready --execute 之间,需要人工读取状态、比较中文源并选择命令。现在仓库已经新增统一恢复入口,由程序根据现有状态、execution evidence、pre-write 基线和生产只读数据自动选择安全策略。这里的统一恢复主要针对已经形成稳定失败证据的单篇文章;如果本地执行被 Ctrl+C 中断,状态停在 translation_started / execution_in_progress,需要先完成孤立 attempt 与 execution evidence 的协调,再回到正常恢复路径。

1. 先只读预览恢复策略

Bash
cd ~/code/wordpress-ai-excerpt-backfill

python3 bin/history-migration.py recover \
    --post-id 文章ID

预览模式不会写入状态,也不会重新执行文章。它会显示当前状态、真实错误、execution evidence、生产中文源是否变化、恢复策略、将执行的步骤以及下一步。

2. 确认后自动恢复并执行

Bash
python3 bin/history-migration.py recover \
    --post-id 文章ID \
    --execute

recover --execute 会在一次调用中完成必要的恢复步骤,只处理目标文章,不会重新执行同批已经 completed 的其他文章。正常情况下,不再需要随后手工运行整个批次的 run-ready --execute

3. recover 会自动选择的策略

  • completed → none:文章已经完成,立即安全返回,不连接 SSH、不重新执行、不写入状态,显示“下一步:无”;
  • translation_failed + 中文源未修改 → resume:复用现有安全基线,从失败位置继续执行;
  • translation_failed + 中文标题或正文已修改 → restart_from_current:归档旧 execution / pre-write,以当前生产版本重建基线并在同一次调用中重新执行目标文章;
  • excerpt_failed + 中文源未修改 → retry_excerpt_generation:摘要生成在写入 WordPress 以前失败,中文源、空摘要和英文基线仍匹配时,只重新生成中文摘要并继续安全执行;如果 evidence 已明确是 1301 等确定性内容安全过滤,则不要继续盲目重试,改走本节后面的 ChatGPT 人工兜底;
  • production_readonly_unavailable → blocked:未取得生产数据,无法判断中文源是否变化,不写入任何状态;
  • 不满足现有安全检查的情况 → blocked:明确打印原因,不通过无条件重置绕过检查。

原有 resumerestart-from-current --apply 等命令仍然保留用于兼容和专项排障。正常失败日常仍优先使用 recover;只有中断留下的 translation_started / execution_in_progress、孤立 attempt 或计数漂移等状态协调问题,才进入后面的专项恢复流程。

4. 真实案例:6965 的 Protected Token 校验失败

在批次 mixed-syntaxhighlighter-20260804-02 中,19 篇一次完成,中文文章 6965 在英文覆盖翻译阶段连续返回 HTTP 500。旧分类把它标记为 transient_network_error,导致批次内重试三次,随后单篇 resume 又失败一次。

读取 data/backups/single-candidate/chinese-6965.execution.json 后才确认真实错误是 swq_full_article_token_validation_failed:预期 23 个保护标记,实际得到 27 个,额外出现 4 个 SWQINLINE...END。这不是网络波动,而是 GLM-5.2 对行内保护标记的确定性重复输出。

人工简化中文标题、普通段落和图片 alt 中容易触发行内保护的技术字符串后,统一恢复流程应当只需要:

Bash
python3 bin/history-migration.py recover \
    --post-id 6965

python3 bin/history-migration.py recover \
    --post-id 6965 \
    --execute

程序会检测中文源已经变化,自动选择 restart_from_current,重建基线并只执行 6965。该文章最终一次完成,批次恢复为 20 篇全部 completed

这次案例也推动了错误分类修复:以后 protected_token_validation_error 首次失败后立即停止,并直接显示真实 codemessage 和 evidence 路径,不再伪装成普通 HTTP 500 网络错误。

5. completed 状态必须是安全空操作

统一恢复命令上线后的第一次真实冒烟测试还发现:即使文章已经是 completed,旧实现仍会继续查询生产环境;一旦 SSH 恰好不可用,就把已完成文章错误显示为 blocked

现在判断顺序已经调整为先读取本地工作流状态。遇到 completed 立即返回 strategy=none,不查询生产环境、不比较中文源、不重建基线、不重新执行,也不修改 execution evidence。

Plaintext
模式: execute
文章: zh=6965 en=13099
当前状态: completed
真实错误: 无
生产中文源: 无需检查
恢复策略: none
将执行: 无
写入操作: 否
原因: 文章已经完成,无需恢复
下一步: 无

6. Ctrl+C 中断后停在 translation_started / execution_in_progress

2026 年 8 月 8 日的真实恢复中又补充了一个统一 recover 之外的边界场景:在 resume --execute 已经发出远程请求后按下 Ctrl+C,本地 Python 进程会退出,但已经发到 WordPress / GLM 的远程请求不保证同步取消。因此不能把 Ctrl+C 理解成“本次 attempt 从未发生”。

如果之后看到 workflow 仍是 execution_in_progress,execution evidence 为 translation_started,而直接执行 resume 得到 selected_count: 0,不要删除状态文件,也不要手工修改 JSON。先执行 show-current --json 核对当前批次与真实 evidence,然后对中断发生的 resume 阶段做 attempt 对账。

Bash
python3 bin/history-migration.py show-current --json

python3 bin/history-migration.py reconcile-attempts \
    --post-id 文章ID \
    --stage resume \
    --json

先看 preview。只有确认 eligible=trueplanned_count 符合预期,并且目标确实是这篇文章后,才增加 --apply

Bash
python3 bin/history-migration.py reconcile-attempts \
    --post-id 文章ID \
    --stage resume \
    --apply \
    --json

reconcile-attempts 负责把 started / terminated / orphaned attempt 与当前 recovery generation 对齐。2026 年 8 月 8 日修复后,这些统计只在当前 recovery_generation 中计算:旧 generation 的 attempt 仍保留用于全生命周期审计,但不会继续占用新 generation 的重试额度。

如果旧逻辑已经把历史 attempt 错误写入当前 retry_counts,即使已经没有 orphaned attempt,preview 仍可能显示 counter_drift=truereconciliation_action=counter_drift_correction。这种情况下 apply 只修正当前 generation 的计数,不改 lifetime_retry_counts、execution evidence 或其他文章。

attempt 对账完成后,再预览 execution evidence 与协调状态的同步:

Bash
python3 bin/history-migration.py sync-execution \
    --json

只有确认 items 中计划同步的文章符合预期,例如从 blocked / execution_in_progress 恢复到 ready_for_translation_resume,才执行:

Bash
python3 bin/history-migration.py sync-execution \
    --apply \
    --json

随后必须先做 resume Preview,而不是直接再次发送模型请求:

Bash
python3 bin/history-migration.py resume \
    --post-id 文章ID

只有看到 selected_count: 1allowed_count: 1 后,才执行:

Bash
python3 bin/history-migration.py resume \
    --post-id 文章ID \
    --execute

如果这一步在 WordPress REST 预检阶段返回 403 rest_cookie_invalid_nonce,先更新 Cookie / REST Nonce,再继续恢复,不要连续重试。一次失败的正式执行也可能被记录进当前 generation 的重试计数;认证已经明确失效时,继续重试只会浪费重试额度。

这一专项流程只用于“本地中断导致状态没有正常收敛”的情况。正常的 translation_failed 仍然优先交给 recover,不要因为出现过一次 Ctrl+C 就把所有失败文章都改走 reconcile-attempts


7. GLM 1301 内容安全过滤:使用 ChatGPT 人工兜底

如果摘要生成已经通过 execution evidence 明确确认是 1301 内容安全过滤,并且重复 recover 只会再次触发同一个确定性拒绝,就不再继续消耗 GLM 请求。这里的目标也不是修改原文章去“绕过”模型,而是保留历史文章本身,由 ChatGPT 作为外部人工兜底完成这一篇。

人工兜底时,需要先补齐中文摘要,再由 ChatGPT 根据完整中文 Gutenberg 源生成英文标题、英文摘要和完整英文正文。人工保存到 WordPress 后,不直接手工修改本地 JSON,也不删除原来的 excerpt_generation_failed evidence,而是先运行只读 Preview:

Bash
cd ~/code/wordpress-ai-excerpt-backfill

python3 bin/history-migration.py mark-manual-completed \
    --post-id 文章ID

mark-manual-completed 会重新读取生产环境,检查中英文 post ID、publish 状态、Polylang 双向关系以及英文标题和正文。对于 excerpt_failed,还会额外要求中文 post_excerpt 非空;如果中文摘要还没有补齐,会明确显示 Chinese excerpt is empty,并保持 允许人工完成: 否

只有 Preview 明确显示“允许人工完成: 是”“写入操作: 否”以后,才执行确认:

Bash
python3 bin/history-migration.py mark-manual-completed \
    --post-id 文章ID \
    --confirmed

--confirmed 不会再次修改 WordPress,也不会把原来的自动执行 evidence 改写成成功。它只在本地 coordination state 中记录 workflow_status=completed,并增加 manual_completion.status=confirmedmanual_completion.method=manual_external。这样既能让批次正常收敛,又能完整保留最初的 GLM 1301、重试和 recovery 历史。

这是一条异常兜底路径,不替代正常的 run-ready --execute。正常文章仍由仓库自动生成摘要和覆盖英文译文;只有模型明确拒绝、继续自动重试没有意义时,才切换到 ChatGPT + 人工保存 + mark-manual-completed

十四、真实验证结果与流程演进

第一批:mixed-syntaxhighlighter-20260729-01

共 20 篇。人工 Gutenberg 规范化、mark-converted、生产只读验证和正式执行最终全部完成。过程中出现过 GLM HTTP 400、英文覆盖翻译 HTTP 500、Polylang SSH timeout,当时均通过有限重试或单篇恢复完成。

第二批:mixed-syntaxhighlighter-20260730-01

共 20 篇。第一次生产只读验证 18 篇通过、2 篇因 batch read-only SSH query timed out 失败,但两篇仍停留在 awaiting_readonly_validation,有限重试后全部通过。正式执行最后出现 1 篇 translation_failed,当时使用单篇 resume 完成。

第三批:mixed-syntaxhighlighter-20260731-01

共 20 篇。执行前首先遇到 WP_REST_NONCE 失效导致多篇 REST 预检连续 403,重新设置 Nonce 后恢复。随后 8819 因英文标题翻译失败进入 translation_failed;人工修改中文标题和正文后,旧执行基线失效,直接 resume 被安全拦截。

当时新增并实际验证了 restart-from-current,以当前生产版本重建 recovery generation 后完成单篇执行。这一案例证明了恢复必须区分中文源是否变化,但旧流程仍要求人工选择多条命令。

后续案例:mixed-syntaxhighlighter-20260804-02

该批次 20 篇中,19 篇正常完成;6965 因 Protected Token 校验失败停在 translation_failed。这一案例进一步暴露了 HTTP 500 分类过于粗糙、确定性错误被无效重试,以及恢复命令过于分散的问题。

完成错误分类、SSH 255 有限重试和统一 recover 改造后,相关定向测试 21 项全部通过,全量测试由 402 项增加到 404 项并全部通过;真实环境中 6965 恢复完成,completed 的 preview 与 --execute 安全空操作也验证通过。最终改动以提交 52d6fac 推送到远程仓库。

2026 年 8 月 14 日案例:mixed-syntaxhighlighter-20260814-03

这一批 20 篇中,19 篇自动完成,中文文章 7756 在中文摘要生成阶段连续返回 HTTP 400。最初批次内三次均为 400,第一次 recover --execute 又遇到一次独立的 TimeoutError,随后再次恢复时仍返回 400。进一步读取最新 execution evidence 后确认,智谱真实响应为 error.code=1301,并带有内容安全过滤信息。

离线使用生产代码 extract_excerpt_source() 检查后,7756 的原始正文为 8228 字符,真正送入摘要模型的 cleaned content 只有 892 字符,完整 payload 约 3427 UTF-8 字节,没有触发 20k / 28k 截断。因此这次没有证据支持“输入过长”,最强证据就是模型内容安全过滤。

最终没有为了通过 GLM 而修改历史文章,而是由 ChatGPT 人工生成中文摘要、英文标题、英文摘要和完整英文正文,保存到 WordPress 后先执行 mark-manual-completed --post-id 7756 只读验收,再使用 --confirmed 收敛状态。最终 7756 记录为 workflow_status=completedmanual_completion.method=manual_external,原始 excerpt_generation_failed evidence 继续保留;该批次恢复为 completed=20remaining=0

这些案例说明,是否能开始下一批不应依据历史累计的 retry_exhausted,而应以当前 failed=0blocked=0remaining=0、“最新未完成批次: 无”和“建议创建下一批: True”为准。


十五、现在每天真正需要做的固定流程

经过多批真实运行和统一恢复改造以后,日常操作可以压缩为下面九步:

  1. 执行 statussummary,确认是否存在未完成批次;
  2. 只有在“建议创建下一批: True”时才从剩余空摘要候选中创建新的固定批次,每批最多 20 篇;
  3. 设置当天的 batch_idcsv_file
  4. 一次性输出当前批次后台编辑地址;
  5. 逐篇检查当前结构:已经是干净 Gutenberg 的文章只确认即可;Classic / Mixed / unknown 规范化为 Gutenberg;如仍有 SyntaxHighlighter 或其他旧代码结构,再按现有规则迁移和核对;
  6. 批量执行 mark-converted
  7. 检查 Cookie / REST Nonce 后执行生产只读验证;变量非空不代表认证仍有效,遇到 403 rest_cookie_invalid_nonce 时先更新认证信息,对仍停留在 awaiting_readonly_validation 的 SSH 偶发失败再有限重试;
  8. 确认 WP_ADMIN_COOKIEWP_REST_NONCEZHIPU_API_KEY 后执行 run-ready --execute
  9. 最终用 run-ready Preview、statussummary 同时确认 completed、remaining、失败状态和完整性,再决定是否开始下一批。

如果正式执行后已经形成稳定的单篇失败状态,不再先手工判断 resumerestart-from-current。统一执行 recover --post-id 文章ID 查看策略;只有程序明确提示需要人工修改中文文章时才进入 WordPress,保存后再执行同一条命令并增加 --execute。如果 evidence 已明确确认是 1301 等确定性的模型内容安全过滤,继续自动重试没有意义,则按第十三节的 ChatGPT 人工兜底流程补齐中文摘要和英文内容,再用 mark-manual-completed Preview / --confirmed 收敛为 completed。如果是 Ctrl+C 等本地中断留下 translation_started / execution_in_progress,则属于状态协调异常,先按第十三节的专项流程执行 reconcile-attemptssync-execution,再回到 resume Preview / Execute。

生产验证阶段仍然单独处理:awaiting_readonly_validation + SSH timeout → 有限重试;validation_failed → 修复后 validate-live --refresh。正式执行阶段的失败优先统一交给 recover;只有模型明确拒绝、正常自动恢复已经没有意义时,才进入 ChatGPT 人工兜底。


总结

到 2026 年 8 月 12 日,前面的 Gutenberg + Code Block Pro、Gutenberg + SyntaxHighlighter、Mixed + SyntaxHighlighter 以及最后 5 篇异常文章已经全部完成,历史迁移状态达到 45 个固定批次、857 篇文章全部 completed。随后生产只读盘点又确认还有 424 篇符合处理条件的中文空摘要历史文章,因此同一套 SOP 继续向剩余历史文章扩展。

这一阶段真正变化的只有候选入口:不再要求文章必须属于 Mixed 或包含 SyntaxHighlighter,而是选择所有“中文已发布、摘要为空、存在已发布英文对应文章、尚未进入固定批次”的历史文章。固定批次、人工确认、mark-converted、生产只读验证、摘要生成、英文覆盖翻译、recovercompleted 状态机都继续沿用。

Mixed Gutenberg + SyntaxHighlighter 阶段并不是前一套迁移方案失效,而是候选文章本身多了一层 Classic / Mixed 历史结构。原来的 fixed batch、人工转换、生产验证、摘要生成、英文覆盖翻译和 completed 状态机仍然可以继续使用,只需要把整篇 Gutenberg normalization 纳入人工确认和生产验证。

随着真实批次继续推进,SOP 又补齐了四层关键能力:一是根据 WordPress JSON 响应区分真正的暂时性网络错误与确定性的 Protected Token 校验错误,并进一步识别智谱嵌套 error.code=1301 的内容安全过滤;二是通过统一 recover 命令自动判断中文源是否变化,并选择 resumerestart_from_currentretry_excerpt_generationblockednone;三是针对 Ctrl+C 等本地中断留下的 translation_started / execution_in_progress,通过 generation-scoped reconcile-attemptscounter_drift 修正和 sync-execution 让状态重新收敛;四是在模型因确定性内容安全过滤无法继续时,允许由 ChatGPT 完成人工摘要和英文翻译,再通过 mark-manual-completed 安全确认外部完成,同时保留原始失败 evidence。

现在日常主流程仍然是“创建固定批次 → 人工转换 → 生产验证 → 批量执行 → 汇总确认”。正常单篇失败仍优先通过统一 recover 预览和执行;只有本地中断、孤立 attempt、execution evidence 与 workflow 不一致等少数状态协调异常,才需要进入专项恢复;模型明确返回 1301 等确定性拒绝时,则可以切换到 ChatGPT 人工兜底并用 mark-manual-completed 收尾。整个过程中都不应通过删除 state、清空 evidence 或手工修改 JSON 来强行推进状态。

每天批量处理 20 篇 WordPress 历史文章:代码块迁移、摘要补全与英文覆盖翻译完整 SOP GLM-5.2 整篇翻译再次出现 Protected Token 校验失败:从尾部 Token 丢失到 Plaintext 结构重排的完整排查记录

需要长期技术维护或远程问题排查?

我是拥有 15+ 年经验的 PHP / Go 后端工程师,长期关注已有系统维护、Bug 修复、性能优化、服务器排查、WordPress 网站维护和小功能迭代。

如果你的项目遇到以下情况,可以先从一次小问题排查开始合作:

  • ✅ PHP / Laravel / Yii2 老项目无人维护
  • ✅ Go / Gin 后端接口需要排查或优化
  • ✅ WordPress 网站访问慢、报错或插件冲突
  • ✅ Nginx / MySQL / Redis / Linux 服务器异常
  • ✅ CDN / Cloudflare / DNS / HTTPS 配置问题
  • ✅ 需要长期远程技术支持或兼职维护

更多介绍请查看:关于我 & 合作

微信:13980074657
邮箱:shuijingwanwq@gmail.com
Telegram:@shuijingwan
GitHub:https://github.com/shuijingwan