devmaker.net
start/ 3d-printing/ klipper-bare-g92-shifts-z-origin-g28
3D Printing

Klipper: Bare G92 Shifts Z by 87 mm

After a filament change, my Klipper printer printed 87 mm above the bed because two macros contained G92 instead of G92 E0. Even more surprising: a full homing run with the eddy probe didn't reset the shifted Z origin either.

Harry_im_Homelab31 (Portrait)
Harald
2026-11-01 · ~11 min read
Klipper: Bare G92 Shifts Z by 87 mm

I had changed filament the usual way, using the UNLOAD_FILAMENT and LOAD_FILAMENT macros, and then started a print. The printer homed obediently, ran Z-tilt and bed mesh – and then printed in mid-air.

The cause was a missing argument in my printer.cfg: G92 instead of G92 E0. More interesting than the cause, though, is why Klipper doesn't repair it on its own: I was firmly convinced that a G28 straightens out any shifted origin. It doesn't – at least not for Z when homing via the eddy probe.

How it showed up

It started with a harmless question: why does the bed move down about 60 mm after Z homing and straight back up again? Shortly afterwards, the printer was printing into thin air.

A look at the running instance showed two Z values that didn't match. At that moment the toolhead was at the park position X200/Y395:

bash
toolhead.position          Z = 102.601   # where the machine actually is
gcode_move.gcode_position  Z =  15.320   # where the G-code thinks it is

87 mm difference between what the machine does and what the G-code assumes. That is no longer a calibration error – that is a different coordinate system.

What I investigated

The formula from the source code

Klipper converts G-code coordinates in gcode_move.py. The relationship is simple:

bash
gcode_position = last_position − base_position

Plugging in the two live values gives a base_z of 87.28. So something had shifted the Z origin of the G-code coordinate system upwards by a good 87 mm.

Sanity-checking the number

Before looking for the culprit, I wanted to know whether the 15.32 was even plausible. It is: 0.32 mm first layer plus the 15 mm by which END_PRINT lifts the head at the end (G1 Z15). So the number matches G-code that ran normally – just 87 mm above the bed.

What happened before the print

Klipper's gcode_store keeps the most recently executed commands. It contained the sequence that explained everything:

bash
13:47:57  UNLOAD_FILAMENT
13:48:45  LOAD_FILAMENT
13:49:11  SDCARD_PRINT_FILE

And the same pattern had already occurred in the morning. Filament change, then print start – twice on the same day.

Live experiments

Instead of reading further, I tried out on the machine what the individual commands do to the coordinate system:

  • A bare G92 sets the G-code position to 0/0/0/0 – that is, X, Y, Z and E.
  • G28 X Y resets base_x and base_y.
  • A full G28 and even a raw G28.1 Z leave base_z at 87.294.

That last point was the real shock.

Dead ends

"It's the G28 wrapper"

The printer runs the G28 wrapper from the BTT Eddy package. When all I saw was "60 mm down and back up", it was natural to suspect the wrapper or a configured travel height. But the numbers didn't fit that explanation – after that, the shifted origin turned up.

"The G92 is in the print file"

A G92 with a Z value in the print file would have had exactly this effect. A grep over the file disproved that: all 282 G92 lines in it read G92 E0. The slicer only resets the extruder, as it should.

"Homing cures the offset"

This was the most stubborn misconception, because it sounds so obvious. Homing means the machine finds its origin again, after all. Any offset should be gone afterwards.

It wasn't. After a full G28, base_z was still at 87.294. The machine was at Z 97.294 while the G-code assumed Z 10. Homing, Z-tilt and bed mesh all ran through without noticing that the G-code's Z axis starts 87 mm above the bed.

Homing is not a reset button for the coordinate system

In my setup, G28 via the eddy probe does not reset the G-code Z origin. An offset in base_z that arises before the print starts survives the entire start sequence.

The cause

In printer.cfg, both filament macros ended with a bare G92. What I meant was G92 E0:

yaml
[gcode_macro LOAD_FILAMENT]
gcode:
    G91
    G1 E60 F1800
    G1 E60 F180
    G92            # meant: G92 E0

[gcode_macro UNLOAD_FILAMENT]
gcode:
    G91
    G1 E-120 F1800
    G92            # meant: G92 E0

Without an axis argument, G92 does something completely different than with one. In gcode_move.py (lines 189/190 in my version) it reads:

python
base_position[:4] = last_position[:4]

So X, Y, Z and E are all zeroed at the current position. During the filament change the head was at Z 87.28 – and that is exactly where the new "zero" for the G-code lay from then on.

There was a second detail: both macros switched to relative coordinates with G91 and left it that way.

The fix

Filament macros without G92

I rebuilt both macros (pattern only, the exact lines may differ) so that they save the G-code state, move the extruder in relative mode and restore the state afterwards – entirely without G92. That also means no G91 is left hanging.

yaml
[gcode_macro LOAD_FILAMENT]
gcode:
    SAVE_GCODE_STATE NAME=<name>
    M83                       # extruder only, relative
    G1 E60 F1800
    G1 E60 F180
    RESTORE_GCODE_STATE NAME=<name>

[gcode_macro UNLOAD_FILAMENT]
gcode:
    SAVE_GCODE_STATE NAME=<name>
    M83
    G1 E-120 F1800
    RESTORE_GCODE_STATE NAME=<name>

A safeguard line after homing

Fixing the macros removes this one trigger. But G28 still doesn't catch a shifted Z origin – no matter where it comes from. That's why START_PRINT now calls a new macro, _Z_FRAME_RESET, right after homing:

yaml
[gcode_macro START_PRINT]
gcode:
    ...
    G28
    _Z_FRAME_RESET
    ...

The macro itself is short:

yaml
[gcode_macro _Z_FRAME_RESET]
description: Hard-resets gcode Z to the homing origin (see START_PRINT)
gcode:
    G90
    G92 Z{printer.toolhead.position.z - printer.gcode_move.homing_origin.z}

It sets the G-code Z position to the real toolhead position minus any Z offset that may be set (homing_origin.z). That gets rid of a base_z shifted by a bare G92 without losing the Z offset.

There's a reason this is a separate macro and not a few lines directly in START_PRINT: Klipper renders a macro completely before the first line is executed. My first attempt failed precisely because of that. My conclusion: a separate macro is only rendered when it is called – that is, after homing.

Before the change, I backed up the old configuration as printer.cfg.vor-g92fix.

How I verified it

After a restart, I deliberately triggered the error again to see whether the safeguard line really catches it:

Stepbase_zResult
after G28 −0.0044 equals homing_z – clean
Sabotage: G92 Z50 −40.0044 offset reproduced
after the new safeguard line −0.0044 back to the starting value
Final check 0.098320 equals homing_origin, difference 0

The sabotage test is the most important part: without it, I would only have shown that nothing goes wrong in the normal case. With it, it's proven that the safeguard line actually brings back an artificially shifted origin.

The first real print start on 31 Aug put the first layer at machine Z 0.346: G-code Z 0.240 plus offset 0.106. Exactly where it belongs.

What remains open

I only skimmed the source code (homing.py and safe_z_home.py) to find out why Z homing via the eddy probe doesn't reset base_z. For the printer it no longer matters, because _Z_FRAME_RESET catches the case. But I haven't understood the mechanism yet.

Check: is base_z correct after homing?

My recommendation from this case: if you have filament or other helper macros containing G92, check whether an axis really follows it. And after a G28, it's worth looking at gcode_move.base_position in the running instance: if Z there shows anything other than the homing offset, the printer is printing in a shifted coordinate system.

Test setup and scope

Printer: HevORT, Klipper v0.13, BTT Eddy with the G28 wrapper from the BTT Eddy package.

The numbers come from measurements on this machine. The behavior of G28 with respect to base_z applies to this setup; I haven't checked whether it's the same with other probes or endstops.

// related posts

> echo "your thoughts" >> klipper-bare-g92-shifts-z-origin-g28.responses

Post your comment

Required for comment verification