While continuing the Chinese-to-English migration of historical WordPress articles recently, I encountered a rather unusual article.
The Chinese ID of this article is 4652, and the English article ID is 14655. It mainly documents the troubleshooting process for the timeout alert of the WeChat third-party platform component_verify_ticket. The article is quite old, and its body contains not only standard Chinese explanations but also PHP code, server logs, file lists, paths, timestamps, and a large amount of historical debug output.
It belongs to the following historical migration batch:
mixed-syntaxhighlighter-20260807-03
This batch contains 20 articles in total. The other articles were completed one after another, but 4652 kept returning HTTP 400 when calling GLM-5.2 for full-text translation.
I had previously documented the impact of historical article migration and Plaintext structure on full-text translation:
But this time, the issue was not a Protected Token validation failure; instead, the request was rejected directly at the GLM-5.2 layer.
1. GLM-5.2 consistently returns HTTP 400
The most critical error upon failure was:
prompt_client_error: Bad Request (400) - 系统检测到输入或生成内容可能包含不安全或敏感内容,请您避免输入易产生敏感内容的提示语,感谢您的配合。
The failure status recorded by the historical migration program also pointed to the same issue:
error_summary:
prompt_client_error: Bad Request (400) - 系统检测到输入或生成内容可能包含不安全或敏感内容,请您避免输入易产生敏感内容的提示语,感谢您的配合。
reason:
wordpress_api_error
Based on the error message, it is easy to first suspect that the article contains content triggering the model’s safety detection.
However, this article is essentially just a normal technical troubleshooting record, focusing on the WeChat third-party platform, PHP, Yii, Redis, HTTP request durations, and server logs. There was no intentional submission of sensitive content.

When encountering this situation, the first step is not to repeatedly modify the Prompt immediately, but to confirm:
Was it the title, the excerpt, or the body that triggered the HTTP 400?
2. Confirming via Trace that only the body failed
The current GLM-5.2 full-text translation tuning plugin logs key execution processes to:
/tmp/swq-glm52-execution-trace.jsonl
After checking the Trace of the failed task, I could see that the title and excerpt had actually completed successfully.
The execution flow was roughly as follows:
unit_started: title
unit_finished: title
unit_started: excerpt
unit_finished: excerpt
unit_started: content
content_raw_started
...
content_raw_finished: prompt_client_error
From this, it can be determined that:
- Title translation succeeded;
- Excerpt translation succeeded;
- HTTP 400 occurred during the body translation phase.
This narrowed the troubleshooting scope from the entire article down to the body model payload actually sent to GLM-5.2.
After generating further diagnostic data at the time, I obtained:
post_id: 4652
source_chars: 583450
model_payload_chars: 49938
protected_count: 109
plain_region_count: 8
alt_region_count: 18
Where:
source_chars: 583450
indicates that the original body in WordPress was very large, reaching about 580,000 characters.
However, after processing code blocks, Gutenberg structures, and other protected content with placeholders, the body payload actually handed over to the model had already dropped to:
49938
Additionally, the total message length recorded by the HTTP request layer was:
54848
This number is larger than model_payload_chars, which is normal because the actual request includes system prompts, translation rules, and other content in addition to the body payload.
The problem therefore narrowed further down to these roughly 50,000 characters of model-visible content.
3. Initially attempting to adjust the article text
Because Zhipu returned:
系统检测到输入或生成内容可能包含不安全或敏感内容
I initially started with the article itself, adjusting some text that might cause ambiguity.
This included:
- Shortening and adjusting the article title;
- Rewriting certain descriptions;
- Adjusting some image Alt texts;
- Retaining the genuinely meaningful PHP code, server logs, and technical data.
After modifying the Chinese source, I then used:
restart-from-current
to rebuild the migration baseline corresponding to the current production Chinese source.
But the problem did not explicitly disappear as a result.
This indicated that simply looking for “which Chinese word might be sensitive” was likely not the most effective troubleshooting approach.
So I started analyzing directly: what exactly did GLM-5.2 see.
4. The 8 Plaintext Regions start looking suspicious
I further exported the model payload and protection mapping for 4652:
/tmp/swq-4652-model-payload.txt
/tmp/swq-4652-protected-map.json
/tmp/swq-4652-plain-regions.json
/tmp/swq-4652-alt-regions.json
One key number here was:
plain_region_count: 8
This means that 8 Code Block Pro Plaintext blocks in the body were converted into SWQPLAIN regions and remained exposed to the model.
The first Plaintext did indeed contain Chinese that needed processing:
Appid:
Nickname:
Time: 2020-11-27 13:35:05
Content: When the WeChat server pushes component_verify_ticket to the official account service developer, the developer does not return within 5 seconds
Count: 9 times in 30 minutes
For alarm troubleshooting guidelines, please see: ...
This kind of Plaintext contains natural language, so it makes sense to let the model see it.
The problem lay in the remaining blocks.
They largely consisted of content like this:
[root@b21d3b4237a2 runtime]# ls -lt
total 28
drwxrwxr-x 2 nginx nginx 4096 Nov 27 14:35 debug
-rw-r--r-- 1 nginx nginx 305 Nov 27 14:35 msgXml.txt
-rw-r--r-- 1 nginx nginx 1 Nov 27 14:35 decrypt_result.txt
-rw-r--r-- 1 nginx nginx 219 Nov 27 14:35 requestQueryParams.txt
-rw-r--r-- 1 nginx nginx 573 Nov 27 14:35 requestRowBody.txt
-rw-r--r-- 1 nginx nginx 18 Nov 26 17:24 actionReceive.txt
[root@b21d3b4237a2 runtime]#
There were also many similar file lists further down.
This content shared several common characteristics:
- No Chinese;
- Machine output by nature;
- Should not be translated;
- Ultimately required to be preserved verbatim.
At this point, the problem became quite obvious.
5. The old Plaintext strategy had unnecessary model input
The original approach to handling Plaintext was roughly:
As long as the Code Block Pro language belonged to:
plaintextplaintexttxt
a SWQPLAIN region would be created.
If there was no Chinese inside, it would be marked with preserve_source, and the original content would be restored after the model returned.
From the perspective of final output correctness, this approach had no obvious issues.
Even if the model modified this content, the restoration phase would ultimately still use the original content.
But the problem occurred earlier:
Why should these server logs, which will definitely be restored to their original text, still be sent to the model in full?
In other words, the old process was actually:
Original machine logs
→ Sent to GLM-5.2
→ Model generates results
→ Model results not used
→ Original machine logs restored
The model did work that ultimately would not be used.
This might not be noticeable in ordinary articles, but 4652 happened to contain a large amount of such historical terminal output and file lists.
The result was:
- Increased model input length;
- Increased Token consumption;
- Increased context noise;
- And it could expose the model’s content safety detection to a large amount of machine text that actually did not need to be understood.
Therefore, this was more worth optimizing than continuing to modify the Prompt.
6. Re-dividing Plaintext and SWQBLOCK
Ultimately, I did not remove the Plaintext mechanism, but redefined its usage boundaries.
The new rules became:
- Code Block Pro that is not
plaintext/plain/text/txtcontinues to be fully protected asSWQBLOCK; - When Plaintext contains Chinese, continue using
SWQPLAIN; - When Plaintext contains absolutely no Chinese, protect it entirely as
SWQBLOCKdirectly; - Pure machine output no longer enters the model body payload;
- After translation is completed, the original Code Block Pro content is still restored.
After doing this, the boundaries between the two types of content became clear.
Content that genuinely contains natural language:
Chinese description + time + status information
continues to allow the model to translate.
While this kind of content:
ls -lt
File lists
Shell output
Paths
Server logs
Timestamp lists
Pure English machine status
no longer lets the model see the internal body.
For the model, they are reduced to something like:
SWQBLOCK00000xEND
as a placeholder.
7. Adding tests before deploying to production
This time, I did not modify the production code and immediately retry.
Locally, I first added regression tests related to Plaintext, focusing on verifying:
- Standard Code Block Pro like PHP still converts to
SWQBLOCK; - Plaintext containing Chinese still converts to
SWQPLAIN; - Plaintext without Chinese converts to
SWQBLOCK; - Pure machine logs, paths, and file lists no longer enter the model payload;
- Original blocks can be restored verbatim after translation finishes;
- The original Protected Token validation continues to work correctly.
The test results all passed:
swq-plaintext-structure-test.php: pass
swq-token-validation-test.php: pass
swq-adjacent-token-repair-test.php: pass
swq-tail-struct-token-repair-test.php: pass
patch/token-normalization-tests.php: pass
PHP syntax: pass
git diff --check: pass
There is one point here that is quite important to me:
I did not weaken the structural validation strength just to bypass HTTP 400.
The structures that should be protected are still strictly protected.
What actually changed was:
Which content is worth entering the model’s context.
8. Massive changes in Payload before and after the fix
After deploying the new MU plugin to the production server, I generated diagnostic data again for the same 4652.
The original WordPress body had not changed, but the content actually received by the model had visibly shrunk.
| Item | Before Fix | After Fix | Change |
|---|---|---|---|
| WordPress body character count | 583450 | 583450 | Unchanged |
| Model body payload | 49938 | 8876 | Decreased by ~82.2% |
| Protected Count | 109 | 116 | +7 |
| Plaintext Region | 8 | 1 | -7 |
| Alt Region | 18 | 18 | Unchanged |
| HTTP request message length | 54848 | 13786 | Decreased by ~74.9% |
These numbers illustrate the change very intuitively.
Plaintext Region:
8 → 1
And Protected Count:
109 → 116
The difference between the two is exactly 7.
In other words, the original 7 Plaintext blocks without Chinese were all changed to overall SWQBLOCK protection.
Only the one Plaintext Region that actually contained Chinese was left for the model to process.
The model body payload dropped from:
49938
down to:
8876
a reduction of about 82%.
In the actual HTTP request sent, the total message length also dropped from:
54848
down to:
13786
a reduction of about 75%.
This also illustrates a very practical issue:
Not all “Plaintext” should be sent to the AI.
The language type in the editor only indicates that it is displayed as plain text; it does not mean that this content necessarily belongs to “natural language that should be translated.”
9. Encountering a Ctrl+C recovery issue while fixing the 400
After the Plaintext fix was completed, I prepared to re-verify 4652.
But during a previous execution, I had pressed Ctrl+C.
Although the local process was interrupted, the request already sent by the server side did not necessarily terminate immediately.
As a result, the historical migration state was left with:
translation_started
and:
execution_in_progress
Subsequently, when resuming this task, a boundary issue with reconcile-attempts was also exposed.
10. recovery_generation did not isolate historical retry counts
The previous migration program already supported:
recovery_generation
When the Chinese source was modified and restart-from-current was executed, it would enter a new recovery generation.
The retry count from the old generation should be transferred to:
lifetime_retry_counts
While the current generation’s:
retry_counts
would start counting from scratch.
The problem was that when reconcile-attempts scanned historical attempt events at the time, it did not fully filter by recovery_generation.
As a result, resume attempts that had already ended in the old generation were counted towards the current generation.
Consequently, the new generation showed this even though it had not actually exhausted its retry count:
resume retry limit exhausted
This issue had nothing to do with the GLM-5.2 HTTP 400 anymore; it was a real bug exposed during the recovery state coordination process.
11. Adding generation isolation and counter drift correction
I then made two minimal adjustments to address this issue.
First, I made it so that:
- started attempt;
- terminated attempt;
- orphaned attempt;
all only undergo recovery reconciliation within the current recovery_generation.
Historical Attempt numbers continue to increment for auditing purposes, but the old generation no longer consumes the new generation’s retry quota.
After fixing this, I discovered another lingering issue.
The old logic had already written the incorrect:
retry_counts.resume = 3
into the state file.
Even if the generation filter was now correct, the program still needed to actively detect:
current_resume_count: 3
corrected_resume_count: 0
this counter drift that had already occurred.
Therefore, I also added:
counter_drift
correction.
The Preview for the real article finally showed:
counter_drift: true
current_resume_count: 3
corrected_resume_count: 0
reconciliation_action: counter_drift_correction
eligible: true
planned_count: 1
After the correction, I used:
sync-execution
to restore the migration workflow to:
ready_for_translation_resume
The relevant historical migration tests ultimately reached:
124 tests passed
This can be considered a real recovery process boundary issue that was discovered and fixed along the way while resolving the 400.
12. Finally encountering a WordPress REST Nonce expiration
Once the state was finally restored, I executed again:
python3 bin/history-migration.py resume \
--post-id 4652 \
--execute
As a result, this time it did not even enter GLM-5.2.
The WordPress REST API preflight directly returned:
HTTP request failed with status 403:
rest_cookie_invalid_nonce:
Cookie check failed
That is:
The WordPress REST Nonce in the current terminal environment had expired.
It is important to note that:
The presence of environment variables does not mean that the Cookie and Nonce inside them are still valid.
So I retrieved the Cookie and X-WP-Nonce again from the same successful WordPress REST request, and then overwrote them in the current terminal:
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
I then synchronized the execution state again:
python3 bin/history-migration.py sync-execution \
--apply \
--json
And performed a Resume Preview:
python3 bin/history-migration.py resume \
--post-id 4652
Finally, I got again:
Mode: preview
Integrity: ok
selected_count: 1
allowed_count: 1
- mixed-syntaxhighlighter-20260807-03 zh=4652 allowed=True blocked=-
This indicated that the migration program allowed execution again.
13. Finally completed after re-execution
Finally, I ran again:
python3 bin/history-migration.py resume \
--post-id 4652 \
--execute
This time, the result was:
Mode: execute
Integrity: ok
selected_count: 1
- zh=4652 result=completed category=completed returncode=0 error=

After completion, I ran a Resume Preview again:
python3 bin/history-migration.py resume \
--post-id 4652
And got:
Mode: preview
Integrity: ok
selected_count: 0
allowed_count: 0
Here, 0/0 is no longer a failure.
It is because 4652 has already entered:
completed
Therefore, it naturally will not become a Resume candidate.
14. The final successful Trace verifies the request scale again
Immediately after success, I checked:
/tmp/swq-glm52-execution-trace.jsonl
It can be seen that this task completed a total of 3 GLM-5.2 request units:
titleexcerptcontent
When the body started:
source_chars: 583450
message_text_chars: 13786
The body returned normally after about 30 seconds.
Whereas previously, when the HTTP 400 occurred, the same article’s body request had:
message_text_chars
still at:
54848
In other words, the message scale actually sent to GLM-5.2 in the final successful version had decreased by about 75% compared to the original.
This corroborates with the earlier:
model_payload_chars: 49938 → 8876
mutually.
15. Can we be certain that the HTTP 400 was caused by those 7 Plaintexts?
Here, it is still necessary to distinguish between “strong evidence” and “strict proof”.
What has been confirmed so far is:
- This article had repeatedly experienced the same HTTP 400 before;
- The title and excerpt could succeed, with failures concentrated in the body;
- Simply adjusting the title and some explanatory text did not directly solve the problem;
- Before the fix, there were 8 Plaintext Regions;
- 7 of them consisted of large amounts of server logs and file lists without Chinese;
- After modifying the protection logic, these 7 blocks all became
SWQBLOCK; - The model body payload dropped from
49938to8876; - The HTTP request message length dropped from
54848to13786; - The final real production translation completed successfully.
This entire set of evidence is quite strong.
However, before the final successful execution, I also changed the API Key used by GLM-5.2.
Therefore, this was not a completely strict single-variable experiment, and we cannot directly conclude that:
All previous HTTP 400s were caused by those 7 Plaintext blocks.
A more accurate conclusion would be:
A large amount of machine Plaintext that originally did not need translation was sent to GLM-5.2, which was a clearly existing and completely unnecessary input variable in this article. After changing this content to overall protection, the model input shrank significantly, and the real production translation completed successfully.
Even if it was not the sole cause of the previous HTTP 400, this adjustment itself is still worth keeping.
Because it simultaneously reduced:
- Token consumption;
- Meaningless model context;
- Interference from machine logs;
- The opportunity for the model to reorganize the Plaintext structure;
- Uncertainties in overly long technical articles.
16. Another insight from this troubleshooting
Initially seeing:
Bad Request (400)
and:
系统检测到输入或生成内容可能包含不安全或敏感内容
makes it easy to focus all attention on:
“Which exact sentence triggered the safety detection?”
But the truly effective troubleshooting path this time was actually:
Confirm failed unit
→ Confirm body failure
→ Export model's real Payload
→ Analyze SWQPLAIN and SWQBLOCK
→ Discover large amount of machine Plaintext needing no translation
→ Adjust protection strategy
→ Payload drastically reduced
→ Restore migration state
→ Update expired REST Nonce
→ Re-execute
→ completed
Compared to constantly modifying Prompts or trying to guess sensitive words, this approach is more likely to yield repeatable and verifiable results.
For complex historical WordPress articles, I am now increasingly inclined towards a principle:
Instead of first asking whether the model can correctly process this content, we should first ask whether it is necessary to hand this content over to the model for processing.
For program code, server logs, file lists, paths, and machine output, if it has already been determined that translation is not needed, then directly protecting it in full is usually more reliable than:
“Sending it to the model, and then requiring the model not to modify it”
Human language that genuinely needs translation will continue to be handled by the model.
This time, 4652 went from consistent HTTP 400, to finally:
result=completed
This not only resolved a historical article, but also further clarified how the boundary between Plaintext and machine data should be drawn.
For the large number of historical WordPress articles yet to be processed, this adjustment should also reduce the additional risks posed by similarly long bodies.
For the large number of historical WordPress articles yet to be processed, this adjustment should also reduce the additional risks posed by similarly long bodies.

