Originally published (in Japanese) on my blog: suzuke.page/posts/ender3-v3-troubleshooting
Symptom
My Creality Ender-3 V3 Plus kept throwing a CL2529 error during the first Z leveling (G28 Z) at the start of a print.
- A warning screen pops up during the initial leveling pass
- Error code: CL2529
- Pressing "Continue" re-runs leveling, and the print starts fine the second time
- It happened often enough to be genuinely annoying
Investigation
1. Reading the Klipper log
cat /usr/data/printer_data/logs/klippy.log | grep -i "error\|529\|probe\|tri_"
This turned up:
- Error message:
PR_ERR_CODE_G28_Z_DETECTION_TIMEOUT: G28 Z try probe out of times. - Internal code:
key529 - The bed uses 4 strain-gauge sensors (CH0–CH3) to measure probe pressure
- CH3's sensor readings had abnormal spikes
[PRES_CH3] [..., 324110.000, -980617.000, ...]
While the healthy channels ramped up gradually, CH3 jumped from 324110 to -980617 — a clear noise/glitch, not a real reading.
2. Inconsistent probe results
Comparing three probe attempts:
[G28_FIRST_MMS] [2.763, 3.248, 4.132]
The spread between 2.763mm and 4.132mm is about 1.4mm — way too inconsistent. That inconsistency is the direct cause of the timeout.
3. Disassembling the binary
The core probing logic lives in a compiled binary:
/usr/share/klipper/klippy/extras/prtouch_v1_wrapper.cpython-38-mipsel-linux-gnu.so
Running strings on it surfaced the relevant config parameter names:
strings prtouch_v1_wrapper.cpython-38-mipsel-linux-gnu.so | grep tri_
tri_try_max_times
tri_min_hold
tri_max_hold
tri_hftr_cut
These turned out to be parameters read straight out of printer.cfg.
4. Finding the actual source
The breakthrough: Creality published the Python source for prtouch_v1_wrapper.py under the GPL in December 2025.
- Repo: CrealityOfficial/K1_Series_Klipper
- Commit: e09f36e
That source makes the error logic completely clear.
Error definition:
PR_ERR_CODE_G28_Z_DETECTION_TIMEOUT = {
'code': 'key529',
'msg': 'PR_ERR_CODE_G28_Z_DETECTION_TIMEOUT: G28 Z try probe out of times.',
'values': []
}
Retry limit:
self.tri_try_max_times = config.getint('tri_try_max_times', default=10, minval=0)
If it isn't set in printer.cfg, the default is 10 retries. Setting it to 0 disables the check entirely.
Error-raising logic:
if self.tri_try_max_times != 0:
if use_tri_times == self.tri_try_max_times:
self.print_msg('WHY ERROR',
"FUN:G28_Z Coarse Probe Out of max {} times:{}...".format(...))
self.ck_and_raise_error(True, PR_ERR_CODE_G28_Z_DETECTION_TIMEOUT)
The error is only ever raised when tri_try_max_times != 0. So setting it to 0 skips the check completely.
What "Continue" actually does
I also traced what happens when you hit "Continue" on the printer's screen after the error:
CL2529 error fires
↓
ck_and_raise_error() runs
├─ Nozzle retracts 5mm (safety measure)
└─ raise self.printer.command_error(...) ← shows the warning
↓
User presses "Continue"
↓
SET_KINEMATIC_POSITION → resets current position
↓
G28 Z restarts from scratch
↓
All channels read clean this time → probe succeeds → print starts
In other words, this is a command_error (a warning-level condition), not a Klipper shutdown. Pressing "Continue" just restarts leveling from zero, and the second attempt usually succeeds normally.
The fix
Add tri_try_max_times: 0 to the [prtouch_v2] section of printer.cfg:
ssh root@192.168.0.230
vi /usr/data/printer_data/config/printer.cfg
[prtouch_v2]
# ... existing settings ...
tri_try_max_times: 0
Also clear the error history so it stops showing on screen:
echo '{"list":[]}' > /usr/data/creality/userdata/fault_code/fault_code_info.json
Then restart Klipper to apply it:
/etc/init.d/S55klipper_service restart
Making it survive firmware updates
The problem: firmware updates wipe the setting
Klipper's startup script (/etc/init.d/S55klipper_service) compares the ROM firmware version against the version recorded in printer.cfg on every boot. When a firmware update changes that version, the body of printer.cfg gets overwritten with the ROM defaults (only the SAVE_CONFIG section survives).
The ROM's default [prtouch_v2] section doesn't include tri_try_max_times, so the CL2529 error comes right back after an update.
The fix: an init script that re-applies it automatically
I added /etc/init.d/S56klipper_custom, which runs right after Klipper starts and re-injects the setting if it's missing:
cat > /etc/init.d/S56klipper_custom << 'EOF'
#!/bin/sh
#
# Ensure custom Klipper settings persist across firmware updates.
#
CFG=/usr/data/printer_data/config/printer.cfg
apply_patch() {
if [ -f "$CFG" ] && ! grep -q "tri_try_max_times" "$CFG"; then
sed -i "/^\[prtouch_v2\]/a tri_try_max_times: 0" "$CFG"
return 0
fi
return 1
}
start() {
if apply_patch; then
echo "Custom settings applied, restarting Klipper..."
/etc/init.d/S55klipper_service restart
else
echo "Custom settings already present, no action needed."
fi
}
case "$1" in
start)
start
;;
stop)
;;
restart|reload)
start
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
esac
exit $?
EOF
chmod +x /etc/init.d/S56klipper_custom
Flow:
- Printer boots → S55 starts Klipper (a firmware update may have just overwritten
printer.cfg) - S56 runs immediately after → checks whether
tri_try_max_timesis present inprinter.cfg- If present → does nothing
-
If missing → adds
tri_try_max_times: 0back into[prtouch_v2]and restarts Klipper
If you have other settings you want to survive firmware updates, you can add the same pattern inside apply_patch.
Is this actually safe?
tri_try_max_times: 0 only disables the error check — the probing itself still runs exactly as before. In the source, setting it to 0 just skips the if block that raises the error; it doesn't touch probe accuracy or leveling quality in any way.
The fact that pressing "Continue" almost always succeeds on the retry is further evidence this is transient sensor noise, and that the probe's own retry logic already compensates for it just fine.
A hardware-level improvement worth considering
This fix addresses it in software, but the root cause is that one of the four strain-gauge sensors under the bed — specifically CH3 — is producing abnormal noise.
The config change just prevents that noise from tripping the error; it doesn't fix the sensor itself. Replacing the strain-gauge sensor would likely improve actual probe accuracy and give more consistent leveling, on top of making this error go away for good.
Top comments (0)