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

GLM-5.2 Returns HTTP 400 for Full-Text Translation: A Complete Troubleshooting and Fix from Security Interception to Plaintext Payload Reduction

Figure 1: Historical article 4652 returning an HTTP 400 safety detection error during GLM-5.2 full-text translation

作者:

在

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:

Plaintext
prompt_client_error: Bad Request (400) - 系统检测到输入或生成内容可能包含不安全或敏感内容,请您避免输入易产生敏感内容的提示语,感谢您的配合。

The failure status recorded by the historical migration program also pointed to the same issue:

Plaintext
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.

Figure 1: Historical article 4652 returning an HTTP 400 safety detection error during GLM-5.2 full-text translation
Figure 1: Historical article 4652 returning an HTTP 400 safety detection error during GLM-5.2 full-text translation

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:

Plaintext
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:

Plaintext
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:

Plaintext
/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:

Plaintext
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:

Plaintext
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:

Plaintext
[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:

  • plaintext
  • plain
  • text
  • txt

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:

Plaintext
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:

  1. Code Block Pro that is not plaintext/plain/text/txt continues to be fully protected as SWQBLOCK;
  2. When Plaintext contains Chinese, continue using SWQPLAIN;
  3. When Plaintext contains absolutely no Chinese, protect it entirely as SWQBLOCK directly;
  4. Pure machine output no longer enters the model body payload;
  5. 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:

Plaintext
Chinese description + time + status information

continues to allow the model to translate.

While this kind of content:

Plaintext
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:

Plaintext
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.

ItemBefore FixAfter FixChange
WordPress body character count583450583450Unchanged
Model body payload499388876Decreased by ~82.2%
Protected Count109116+7
Plaintext Region81-7
Alt Region1818Unchanged
HTTP request message length5484813786Decreased by ~74.9%

These numbers illustrate the change very intuitively.

Plaintext Region:

Plaintext
8 → 1

And Protected Count:

Plaintext
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:

Plaintext
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:

Plaintext
retry_counts.resume = 3

into the state file.

Even if the generation filter was now correct, the program still needed to actively detect:

Plaintext
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:

Plaintext
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:

Plaintext
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:

Bash
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:

Plaintext
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:

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

I then synchronized the execution state again:

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

And performed a Resume Preview:

Bash
python3 bin/history-migration.py resume \
    --post-id 4652

Finally, I got again:

Plaintext
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:

Bash
python3 bin/history-migration.py resume \
    --post-id 4652 \
    --execute

This time, the result was:

Plaintext
Mode: execute
Integrity: ok
selected_count: 1

- zh=4652 result=completed category=completed returncode=0 error=
Figure 2: Historical article 4652 finally completing GLM-5.2 full-text translation after re-execution
Figure 2: Historical article 4652 finally completing GLM-5.2 full-text translation after re-execution

After completion, I ran a Resume Preview again:

Bash
python3 bin/history-migration.py resume \
    --post-id 4652

And got:

Plaintext
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:

  • title
  • excerpt
  • content

When the body started:

Plaintext
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:

  1. This article had repeatedly experienced the same HTTP 400 before;
  2. The title and excerpt could succeed, with failures concentrated in the body;
  3. Simply adjusting the title and some explanatory text did not directly solve the problem;
  4. Before the fix, there were 8 Plaintext Regions;
  5. 7 of them consisted of large amounts of server logs and file lists without Chinese;
  6. After modifying the protection logic, these 7 blocks all became SWQBLOCK;
  7. The model body payload dropped from 49938 to 8876;
  8. The HTTP request message length dropped from 54848 to 13786;
  9. 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:

Plaintext
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.

系列导航

关于作者 · 持续学习与技术实践

我是一名拥有 15 年以上开发经验的技术从业者,自 2013 年起持续运营个人技术博客,记录工作与个人项目中遇到的真实问题,以及解决问题过程中的探索、实践和思考。

除了技术写作,我也在持续开发和维护自己的独立项目,探索新技术和新工具在实际场景中的应用。我始终相信,没有不值得去解决的问题,也没有不值得去学习的技术。

如果这篇文章对你有所帮助,欢迎浏览博客中的其他文章,也欢迎交流技术经验与想法。

关于我  ·  GitHub  ·  邮件联系