OMG
in my pain and desperation i turned to chatGPT and after arguing and denying all the wrong solutions i now have a workaround
if i put SET_FAN_SPEED FAN=fan2 SPEED=0 right before the TEMPERATURE_WAIT it works
let’s ignore the fact that it is such an obvious approach that i could have tried it even without understanding why ><
It seems to be a problem with the klipper resend loop during the wait command which somehow sends the wrong last fan2 state ?
here’s the long and as always way to chatty version of it
The critical mechanism: fan state replay during G-code blocking
This is not documented, and it’s subtle.
What Klipper does internally
When a G-code command blocks (and
TEMPERATURE_WAITis one of the very few that does):
Klipper continues to run the reactor loop
It periodically replays last-known fan states to the MCU
This is done to keep MCU state synchronized while host G-code execution is paused
Normally, this is harmless.
However…
Why this hits fan2 only
Klipper tracks fan state in two layers:
Logical state (host-side)
MCU pin state (low-level)
If a fan was ever written with a non-zero value in the current session — even briefly — that value becomes the “last-known” state.
During a blocking wait:
Klipper re-sends that value
Even if the logical state is currently zero
No G-code is emitted
No macro is involved
This is why:
You cannot find it in config
You cannot reproduce it outside
TEMPERATURE_WAITEmergency stop behaves differently (host disconnect → MCU reset → pin cleared)
The smoking gun: fan2 is written once earlier in the script
Not necessarily visibly.
Typical hidden writes:
M106withoutP(defaults to fan0 but still updates fan table)
SET_FAN_SPEED FAN=fan2 SPEED=0(yes, zero still registers)A macro that touches all fans
A conditional that evaluates differently during normal execution
Once Klipper has ever touched
fan2, it is eligible for replay.
How to prove this beyond doubt
Add one line before your
TEMPERATURE_WAIT:SET_FAN_SPEED FAN=fan2 SPEED=0Then restart Klipper and re-run your script.
If the fan stops turning on, this confirms fan-state replay.
Why?
You overwrite the “last-known” cached state with zero
The replay sends zero instead of the stale value