In my previous post, I documented:
“Batch-Processing 20 WordPress Legacy Posts a Day: Complete SOP for Code Block Migration, Excerpt Backfill, and English Overwrite Translation”
That stage mainly covered legacy posts that were already in Gutenberg format but still contained SyntaxHighlighter in the post body.
After completing that stage, I found that quite a few Chinese legacy posts on the site still contained SyntaxHighlighter.
Further inspection confirmed that these posts had not been missed by the earlier scan. They belonged to a different legacy format:
Gutenberg 区块
+
Classic / 经典编辑器内容
+
SyntaxHighlighter
The analyzer classified them as:
editor_format = mixed
In the earlier Gutenberg + SyntaxHighlighter stage, only posts with editor_format=gutenberg were processed, so these Mixed posts were left for the next stage.
In the historical snapshot, there were 717 Mixed candidates in total, and 712 of them had one main issue:
editor-format-mixed
The remaining five also had other code-format or structural issues, so I left them aside for separate handling.
I therefore started the second stage:
Mixed Gutenberg + SyntaxHighlighter
→ Classic / Mixed 内容规范化为 Gutenberg
→ SyntaxHighlighter 转 Code Block Pro
→ 核对代码语言
→ 生产只读验证
→ 自动生成中文摘要
→ 自动覆盖英文译文
→ completed
The overall workflow continues to follow the operating pattern established in the previous post, with only the steps genuinely required for the Mixed stage added.
1. What the Two Repositories Are Responsible For
The workflow still involves two repositories.
1. WordPress Translation Pipeline
/home/wangqiang/code/wordpress-ai-translation-pipeline
Main responsibilities:
- SlyTranslate and GLM translation customization;
- Gutenberg structure protection;
- HTML, code, and shortcode protection;
- placeholder validation;
- full English overwrite translation;
- MU plugins related to the WordPress production environment.
2. Legacy Post Migration Repository
/home/wangqiang/code/wordpress-ai-excerpt-backfill
Main responsibilities:
- manage legacy candidates;
- create fixed batches;
- store Chinese-English post relationships;
- manage manual conversion status;
- run production read-only validation;
- generate Chinese excerpts;
- call the existing English overwrite-translation endpoint;
- store execution evidence;
- perform limited retries for sporadic failures;
- summarize the status of the entire batch.
The overall division of responsibilities remains:
人工处理文章格式
→ wordpress-ai-excerpt-backfill 验证和调度
→ wordpress-ai-translation-pipeline 完成英文覆盖翻译
2. Division of Responsibilities Between Manual Work and the Repository
Repository Responsibilities
In the Mixed stage, the repository is responsible for:
- selecting eligible Mixed + SyntaxHighlighter posts from historical data;
- excluding posts that have already entered an earlier batch or have already been completed;
- sorting them from newest to oldest by publication time;
- fixing at most 20 posts per batch;
- storing Chinese and English post IDs;
- storing the original SyntaxHighlighter count;
- manage manual conversion status;
- verifying that the final post has been normalized into valid Gutenberg content;
- verifying that the SyntaxHighlighter count has dropped to zero;
- validating Code Block Pro;
- automatically generating the Chinese excerpt;
- automatically overwriting the existing English translation;
- performing limited retries for a single-post failure;
- summarizing the final status of the entire batch.
Manual Responsibilities
Manual work is responsible for:
- opening the 20 posts specified by the repository;
- converting Classic / Mixed content to Gutenberg;
- checking the body layout after automatic conversion;
- splitting paragraphs, headings, numbering, and similar structure again when necessary;
- converting SyntaxHighlighter to Code Block Pro;
- checking the language setting of each Code Block Pro block;
- checking images, captions, links, and body structure;
- saving the Chinese post;
- finally confirming that manual processing for the batch is complete.
Manual work does not need to:
- find the next batch of posts manually;
- sort posts by publication time manually;
- generate Chinese excerpts manually;
- click through the English overwrite-translation action one post at a time.
3. Complete State Flow
The normal main flow remains the same as in the previous post:
awaiting_manual_conversion
→ mark-converted
→ awaiting_readonly_validation
→ validate-live
→ ready_for_execution
→ run-ready --execute
→ completed
The differences are mainly in the manual-processing and production-validation stages.
In the Mixed stage, in addition to code-block conversion and language review, I also need to confirm:
整篇 Gutenberg normalization 已完成
Production read-only validation also adds these requirements:
editor_format = gutenberg
classic_outside_blocks = false
Therefore:
Classic 区块已经消失
is not directly equivalent to:
Gutenberg normalization 已经完成
I still need to manually confirm the body structure and layout after conversion.
4. Check Repository Status Before Starting Each Day
Enter the repository:
cd ~/code/wordpress-ai-excerpt-backfill
Run:
python3 bin/history-migration.py status
python3 bin/history-migration.py summary
Focus on:
最新未完成批次
下一步
建议创建下一批
If the output shows:
最新未完成批次: mixed-syntaxhighlighter-XXXXXXXX-XX
建议创建下一批: False
continue and finish the current batch.
Do not create another 20 posts in advance.
Only when the output shows:
最新未完成批次: 无
建议创建下一批: True
建议: all batches complete
should the next batch be created.
5. Create a New Fixed Batch of 20 Mixed Posts
The Mixed stage uses:
bin/build-mixed-syntaxhighlighter-batch.py
Candidate ordering is fixed as:
published_at DESC
chinese_post_id DESC
In other words, newer legacy posts are processed first.
The selection does not prioritize:
SyntaxHighlighter 最少
内容最短
HTML 最简单
人工最好处理
If fewer than 20 candidates remain at the end, all remaining candidates are processed directly.
After confirming that the previous batch is complete:
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
preview_file="data/analysis/gutenberg-syntaxhighlighter-empty-excerpt-preview.csv"
translations_file="data/raw/wordpress-zh-translation-links-20260721.jsonl"
mapfile -t raw_files < <(
find data/raw \
-maxdepth 1 \
-type f \
\( \
-name 'wordpress-zh-posts-20260720*.jsonl' \
-o \
-name 'wordpress-zh-posts-20260721*.jsonl' \
\) \
| sort
)
if [ "${#raw_files[@]}" -eq 0 ]; then
echo "没有找到 WordPress 原始历史快照。"
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"
python3 bin/build-mixed-syntaxhighlighter-batch.py \
"${raw_files[@]}" \
--preview "$preview_file" \
--translations "$translations_file" \
--output "$batch_file" \
--batch-id "$batch_id" \
--maximum 20 \
&& python3 bin/history-migration.py init-state --apply \
&& echo \
&& echo "新批次已经创建:" \
&& echo "batch_id=\"$batch_id\"" \
&& echo "csv_file=\"$batch_file\""
fi
fi
fi
The final output should normally look similar to:
batch_id="mixed-syntaxhighlighter-20260730-01"
csv_file="data/analysis/mixed-syntaxhighlighter-migration-batch-20260730-01.csv"
6. Set Batch Variables and View Admin Edit URLs
For easier daily operation, I set the two variables that change each day separately.
For example:
batch_id="mixed-syntaxhighlighter-20260730-01"
csv_file="data/analysis/mixed-syntaxhighlighter-migration-batch-20260730-01.csv"
The commands below reuse:
$batch_id
$csv_file
so there is no need to search through long commands for dates and batch IDs.
Then output the admin edit URLs for the current batch in one go:
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
This provides all admin links needed for that day’s work at once.
7. Manually Normalize Gutenberg and Migrate SyntaxHighlighter
The main change in the Mixed stage is in this section.
Each post requires two types of manual work:
Classic / Mixed → Gutenberg
SyntaxHighlighter → Code Block Pro
1. Convert Classic to Gutenberg
After selecting a Classic block, use the WordPress option:
转换为区块
In a simple case, for example, when the Classic block contains only:
3. 最终成功运行日志
the conversion can directly produce:
<!-- wp:paragraph -->
<p>3. 最终成功运行日志</p>
<!-- /wp:paragraph -->
Simple content like this usually needs no further adjustment.

For more complex Classic content, a single Classic block can also be split into multiple Gutenberg blocks in one conversion.
For example, the converted result may contain:
Paragraph
Paragraph
Image
Paragraph
Code Block Pro
Paragraph
……
There is no need to recreate every paragraph inside the Classic block manually.

2. Always Check the Layout After Automatic Conversion
This is an important point in actual use.
A successful WordPress conversion to Gutenberg does not guarantee that the body structure is correct.
After a complex Classic block is converted, content that was originally separate, such as:
章节标题
普通段落
1. 第一项
2. 第二项
结论
下一节标题
may be merged into one large Paragraph block.
Other problems may also appear:
原来的换行消失
编号和正文连在一起
小标题变成普通段落
多个逻辑段落合并
Therefore:
已经点击“转换为区块”
does not mean that the manual work is complete.

I then continue with manual cleanup of:
- paragraphs;
- headings and subheadings;
- numbering;
- lists;
- line breaks;
- image positions;
- caption;
- links;
- body-content order.
until the normal reading structure is restored.

The final acceptance criteria should therefore be:
Classic 已转换
+
正文 Gutenberg 结构正常
+
排版正常
+
内容没有遗漏
3. Convert SyntaxHighlighter to Code Block Pro
After the Gutenberg body has been cleaned up, process SyntaxHighlighter.
Normal code, configuration, terminal output, and similar content is migrated to:
Code Block Pro
The final post must satisfy:
SyntaxHighlighter = 0
4. Review the Code Block Pro Language
When the original SyntaxHighlighter block specifies a language, use the corresponding language, for example:
PHP
JavaScript
Bash
JSON
HTML
CSS
SQL
Python
Nginx
YAML
When the original block does not declare a language:
Plaintext
Code Block Pro may inherit the language used by the previous code block, so conversion success alone is not enough; the language must be checked block by block.
5. Checklist Before Saving
Before saving each post, confirm at least:
Classic / Mixed 内容已经规范化为 Gutenberg
自动转换后的排版已经人工检查
SyntaxHighlighter = 0
Code Block Pro 内容完整
Code Block Pro language 正确
图片、caption、链接没有遗漏
正文内容没有重复
内容顺序没有发生异常
不存在 Gutenberg 损坏区块
Do not generate the excerpt manually here, and do not modify the English translation.
8. Record That Manual Conversion Is Complete
After all 20 posts have been processed manually, move them from:
awaiting_manual_conversion
to:
awaiting_readonly_validation
First confirm the batch variables:
echo "$batch_id"
echo "$csv_file"
Then run:
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
Normally, the key output to watch is:
========== 人工转换记录汇总 ==========
待记录:20
成功:20
失败:0
The command no longer prints all historical batches or the complete show-current, preventing the important result from being buried in excessive output.
9. Run Production Read-Only Validation
This stage is responsible for:
awaiting_readonly_validation
→ ready_for_execution
1. Check Environment Variables
for name in WP_ADMIN_COOKIE WP_REST_NONCE
do
if [ -z "${!name-}" ]; then
echo "缺少环境变量:$name"
else
echo "已设置:$name"
fi
done
If either variable is missing:
read -rsp "粘贴 WP_ADMIN_COOKIE,然后回车:" WP_ADMIN_COOKIE
echo
export WP_ADMIN_COOKIE
read -rsp "粘贴 WP_REST_NONCE,然后回车:" WP_REST_NONCE
echo
export WP_REST_NONCE
2. Run Read-Only Validation in Batch
In the second real batch, 18 of 20 posts passed on the first run, while two returned:
batch read-only SSH query timed out
Their states remained:
awaiting_readonly_validation
After validation was retried, both posts passed on the first retry.
So limited automatic retries are built directly into this step.
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)}] "
f"验证通过: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
A normal result should look like:
========== 只读验证最终汇总 ==========
本次待验证:20
本次通过:20
本次失败:0
Then:
模式: preview
完整性: ok
selected_count: 20
allowed_count: 20
In addition to the original code-block checks, the Mixed stage also verifies:
editor_format = gutenberg
classic_outside_blocks = false
SSH Timeouts and validation_failed Are Different Cases
For example, the second batch encountered:
batch read-only SSH query timed out
while the state remained:
awaiting_readonly_validation
This is a sporadic timeout in the production read-only query and can be retried a limited number of times.
If the post actually enters:
validation_failed
do not continue retrying blindly. Inspect and fix the post first.
After fixing it, run:
python3 bin/history-migration.py validate-live \
--post-id 文章ID \
--refresh
10. Generate Excerpts in Batch and Overwrite English Translations
Formal execution requires:
WP_ADMIN_COOKIE
WP_REST_NONCE
ZHIPU_API_KEY
Check:
for name in WP_ADMIN_COOKIE WP_REST_NONCE ZHIPU_API_KEY
do
if [ -z "${!name-}" ]; then
echo "缺少环境变量:$name"
else
echo "已设置:$name"
fi
done
If only the API Key is missing:
read -rsp "粘贴 ZHIPU_API_KEY,然后回车:" ZHIPU_API_KEY
echo
export ZHIPU_API_KEY
After everything is ready:
python3 bin/history-migration.py run-ready \
--batch-id "$batch_id" \
--execute
Progress is displayed in real time during execution:
[1/20] 开始处理:zh=...
[1/20] 处理完成:zh=...
[2/20] 开始处理:zh=...
……
When a single post encounters a sporadic error, the system performs a limited number of retries instead of stopping the entire batch immediately.
The first batch actually encountered:
GLM HTTP 400
英文覆盖翻译 HTTP 500
Polylang SSH timeout
All of them recovered during subsequent limited retries.
There is no need to stop the entire batch manually as soon as one HTTP error appears.
Let the whole batch finish first, then decide what to do based on the final state.
11. Complete Check After the Batch Finishes
After formal execution finishes:
python3 bin/history-migration.py run-ready \
--batch-id "$batch_id"
python3 bin/history-migration.py status
python3 bin/history-migration.py summary
A completed batch should show:
selected_count: 0
allowed_count: 0
At the same time, the batch state must satisfy:
completed = 批次总数
remaining = 0
translation_failed = 0
validation_failed = 0
blocked = 0
integrity = ok
next_action = 当前批次已完成
The global state may then further show:
最新未完成批次: 无
建议创建下一批: True
建议: all batches complete
selected_count=0 Does Not Mean the Batch Is Complete
After the second batch finished executing, I actually saw:
模式: preview
完整性: ok
selected_count: 0
allowed_count: 0
But status also showed:
completed=19
translation_failed=1
remaining=1
下一步: 可以 resume
In other words:
selected_count=0
This only means that there are currently no posts that can be executed directly by run-ready.
It does not prove that:
整个批次已经完成
The final check must include:
completed
remaining
失败状态
next_action
12. Exception States and Recovery
In daily use, do not treat every exception as simply “run it again.”
Different states should follow different recovery paths.
1. awaiting_readonly_validation + SSH timeout
For example:
batch read-only SSH query timed out
and the state is still:
awaiting_readonly_validation
This means only that the read-only query did not complete successfully.
Section 9 already includes limited retry logic, so additional manual handling is usually unnecessary.
2. validation_failed
This indicates a definite validation failure.
Inspect and fix the Chinese WordPress post first.
Then:
python3 bin/history-migration.py validate-live \
--post-id 文章ID \
--refresh
3. translation_failed
At the end of the second batch, one post remained:
zh=9147
status=translation_failed
At that point, the entire batch showed:
completed=19
translation_failed=1
remaining=1
next_action=可以 resume
First inspect the current batch:
python3 bin/history-migration.py show-current
After locating the failed post:
python3 bin/history-migration.py resume \
--post-id 9147 \
--execute
The actual result was:
模式: execute
完整性: ok
selected_count: 1
- zh=9147 result=completed category=completed returncode=0 error=
After checking again, the final result was:
最新未完成批次: 无
建议创建下一批: True
建议: all batches complete
Therefore:
translation_failed
There is no need to rerun the whole batch.
Only run resume。
4. ready_for_execution
If a post is still in:
ready_for_execution
Preview first:
python3 bin/history-migration.py run-ready \
--batch-id "$batch_id"
After confirming:
python3 bin/history-migration.py run-ready \
--batch-id "$batch_id" \
--execute
5. blocked
If the state is:
blocked
First inspect:
last_failure
execution evidence
生产实际状态
Do not rerun it directly.
13. The Fixed Daily Workflow
After validating two real batches, the daily operation can now be reduced to the following fixed workflow.
Step 1: Check Status
cd ~/code/wordpress-ai-excerpt-backfill
python3 bin/history-migration.py status
python3 bin/history-migration.py summary
Confirm:
最新未完成批次: 无
建议创建下一批: True
Step 2: Create a New Fixed Mixed Batch
Maximum:
20 篇
Ordering:
published_at DESC
chinese_post_id DESC
Step 3: Set the Two Variables for the Day
batch_id="mixed-syntaxhighlighter-YYYYMMDD-01"
csv_file="data/analysis/mixed-syntaxhighlighter-migration-batch-YYYYMMDD-01.csv"
Step 4: Output Admin URLs
Get the admin edit URLs for the day’s 20 Chinese posts in one operation.
Step 5: Process Each Post Manually
Classic / Mixed → Gutenberg
↓
检查并修复自动转换后的排版
↓
SyntaxHighlighter → Code Block Pro
↓
核对 Code Block Pro language
↓
保存
Step 6: Record Manual Completion in Batch
Target:
待记录:20
成功:20
失败:0
Step 7: Production Read-Only Validation
The program performs limited retries for posts that remain in:
awaiting_readonly_validation
.
Final target:
本次通过:20
本次失败:0
And:
selected_count: 20
allowed_count: 20
Step 8: Formal Execution
Check:
WP_ADMIN_COOKIE
WP_REST_NONCE
ZHIPU_API_KEY
Then:
python3 bin/history-migration.py run-ready \
--batch-id "$batch_id" \
--execute
Step 9: Final Check
python3 bin/history-migration.py run-ready \
--batch-id "$batch_id"
python3 bin/history-migration.py status
python3 bin/history-migration.py summary
The final state must confirm:
completed = 批次总数
remaining = 0
无失败状态
integrity = ok
Only after these conditions are met should the next batch be started.
14. Results from Two Consecutive Real Batches
To confirm that the workflow works in practice rather than only in theory, I processed two real batches in succession.
First Batch
Batch:
mixed-syntaxhighlighter-20260729-01
Total:
20 篇
Result:
人工 Gutenberg 规范化:20/20
mark-converted:20/20
生产只读验证:20/20
run-ready:
selected_count=20
allowed_count=20
最终:
completed=20
During formal execution, the batch encountered:
GLM HTTP 400
英文覆盖翻译 HTTP 500
Polylang SSH timeout
but all of them recovered through limited retries.
Second Batch
Batch:
mixed-syntaxhighlighter-20260730-01
Again:
20 篇
The first production read-only validation run returned:
通过:18
失败:2
Both failed posts had the same cause:
batch read-only SSH query timed out
and both remained in:
awaiting_readonly_validation
After limited retries:
待重试:2
成功:2
失败:0
Then:
selected_count: 20
allowed_count: 20
After formal execution finished:
completed=19
translation_failed=1
remaining=1
For the only failed post, I ran:
resume
After it succeeded, the final state was:
最新未完成批次: 无
建议创建下一批: True
建议: all batches complete
Across the two batches, the total was therefore:
40 篇
All posts were ultimately completed.
Summary
After the Gutenberg + SyntaxHighlighter stage was completed, the large number of remaining SyntaxHighlighter posts did not mean that the earlier workflow had missed them.
The real reason was that these posts belonged to:
Mixed Gutenberg / Classic + SyntaxHighlighter
So the candidate rules designed only for Gutenberg posts could not be applied directly.
However, once the actual migration began, I did not redesign the entire system.
The existing flow:
fixed batch
→ manual conversion
→ mark-converted
→ readonly validation
→ excerpt
→ English overwrite translation
→ completed
could still be reused.
The only core addition in the Mixed stage was:
Classic / Mixed → Gutenberg
along with the manual confirmation:
--gutenberg-normalization-confirmed
Production validation also adds:
editor_format = gutenberg
classic_outside_blocks = false
After running two consecutive batches covering 40 real legacy posts, I also verified the following cases beyond the normal main flow:
只读 SSH 超时后的有限重试
HTTP 400 / 500 等偶发错误自动重试
translation_failed 单篇 resume
selected_count=0 时继续检查 remaining 和失败状态
The workflow has therefore evolved from a one-time migration test into a repeatable daily SOP. Going forward, I only need to continue processing the remaining Mixed legacy posts with this fixed workflow until the migration is complete.
需要长期技术维护或远程问题排查?
我是拥有 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


发表回复