# Multi-monitor layout not saved: scale and position reset on Omarchy 4
Your external monitor layout, scale and position do not survive a reload on Omarchy 4. Write explicit hl.monitor rules in monitors.lua, then reload.
> **Short answer:** Omarchy 4's Display panel and Super + / only persist scale into the catch-all rule in ~/.config/hypr/monitors.lua, and on a docked laptop a watcher polls every two seconds and re-applies the internal panel's scale from that file, with position "auto" unless a connector-name rule sets one. Write one literal hl.monitor line per output, keyed by connector name, with the scale and position you want, then run hyprctl reload and stop using the scale buttons.
- Applies to Omarchy: 4.0.0 and later
- Status: workaround
- Last verified: 2026-09-16
- Canonical: https://omarchylinux.org/fix/multi-monitor-layout-not-saved/
_Unofficial community page. Not affiliated with 37signals or the Omacom Foundation. Omarchy is a registered trademark of 37signals LLC._

You arrange your screens, set a scale, and it looks right. Then you reload, dock, undock, or reboot, and the layout is back where it started. On Omarchy 4 this is usually not Hyprland losing your config. It is Omarchy writing over it.

## The fix

Checked on 4.0.4. The same steps apply to 4.0.0 through 4.0.3. On 3.x the file is `monitors.conf` and only step 5 applies, see [the Quattro config page](/fix/monitors-conf-replaced-by-monitors-lua/).

1. Get your real output names. Run `hyprctl monitors all`. Note the connector name (`eDP-1`, `DP-2`, `HDMI-A-1`), the mode you want, and the description string if you have two identical panels.

2. Open the file. Use *Setup > Monitors* in the Omarchy menu, which runs `omarchy-launch-config-editor "$HOME/.config/hypr/monitors.lua"`.

3. Write one explicit rule per output, each on a single literal line, above the shipped catch-all. Keep the catch-all last so any screen you did not name still comes up:

   ```lua
   local omarchy_gdk_scale = 1
   local omarchy_monitor_scale = "auto"

   hl.env("GDK_SCALE", tostring(omarchy_gdk_scale))

   hl.monitor({ output = "DP-2", mode = "2560x1440@165", position = "0x0", scale = 1 })
   hl.monitor({ output = "HDMI-A-1", mode = "1920x1080@60", position = "auto-right", scale = 1 })
   hl.monitor({ output = "eDP-1", mode = "preferred", position = "auto-down", scale = 1.6 })

   hl.monitor({ output = "", mode = "preferred", position = "auto", scale = omarchy_monitor_scale })
   ```

4. Prefer relative positions. Use `auto-right`, `auto-left`, `auto-up` or `auto-down` rather than pixel coordinates unless every screen is at scale 1. Absolute coordinates are computed against logical pixels, so a 1080p screen at scale 1.25 is only 864 logical pixels tall and a neighbour pinned at `0x1080` leaves a gap your cursor cannot cross. syskey8 worked the arithmetic out in [discussion #8213](https://github.com/omacom/omarchy/discussions/8213).

5. Set `GDK_SCALE` to the nearest whole number of your largest monitor scale. GTK only honours integers, and apps already running keep the old value until you restart them.

6. Reload with `hyprctl reload`. Do not use `hyprctl keyword monitor ...` from a 3.x guide. Omarchy 4 loads Hyprland through Lua, and that call fails with `keyword can't work with non-legacy parsers. Use eval.`

7. From now on, change scale by editing the rule, not with the Display panel buttons or `Super + /`. Those call `omarchy-hyprland-monitor-scaling`, which is the source of most of the resets described below.

## Verify it worked

Read the live state back and compare it to what you wrote:

```bash
hyprctl monitors all -j | jq -r '.[] | "\(.name) \(.width)x\(.height) scale=\(.scale) at \(.x)x\(.y) transform=\(.transform)"'
```

Then wait ten seconds and run it again. A docked laptop is re-checked every two seconds, so a layout that is going to be overwritten will change within that window. Check `hyprctl configerrors` for a silent parse failure, unplug and replug the external screen, and reboot once. If a scale keeps moving on its own, `~/.local/state/omarchy/monitor-scaling.log` records every change the scaling tool made, including which process asked for it.

## Why it happens

Three separate mechanisms in 4.0.x can write over your layout.

**The scale tool persists to the wrong place.** `bin/omarchy-hyprland-monitor-scaling` applies the new scale live with `hyprctl eval`, then tries to save it by rewriting either `local omarchy_monitor_scale` or the wildcard `output = ""` rule. Both are global. nico-kovacs showed in [#6673](https://github.com/omacom/omarchy/issues/6673) that setting a scale on one screen therefore changes the others within a second or two, because the file write itself triggers a config reload. If your file has explicit per-output rules instead, RonRayReed found in [#8103](https://github.com/omacom/omarchy/issues/8103) that the save silently does nothing, and Douda reported the same revert in [#7242](https://github.com/omacom/omarchy/issues/7242).

**The clamshell watcher re-asserts the internal panel.** `omarchy-hyprland-monitor-watch` runs `omarchy-hyprland-monitor-clamshell` on every monitor event, and every two seconds for as long as a laptop has an active external monitor. That script parses `monitors.lua` with `sed`. It matches only a rule written literally as `output = "eDP-1"`, on one line. mkelk showed in [#7084](https://github.com/omacom/omarchy/issues/7084) that a `desc:` rule, or a rule split over several lines, is invisible to it, so it falls back to the catch-all's scale and forces that onto the panel whenever the live value differs. On 4.0.0 a catch-all still at `"auto"` fell through further to a hardcoded `2`. 4.0.1 stopped that, but the catch-all becomes a number the first time any scale button is pressed, and from then on an unmatched panel snaps back about a second after every change.

**Position is never read from the catch-all.** skoom21 documented in [#7326](https://github.com/omacom/omarchy/issues/7326) that the clamshell script's `read_monitor_position` returns `auto` whenever it cannot find a connector-name rule for the internal panel, and that the live `hyprctl eval` inside the scale tool hardcodes `position = "auto"` as well. The watcher only re-applies position as a side effect of correcting the scale, so on a stock `monitors.lua`, which contains no per-output rule at all, a deliberate offset goes the moment the watcher decides the panel's scale is wrong, and on every scale press regardless. reppiz saw the same rearrangement on 3.4.0 in [#4785](https://github.com/omacom/omarchy/issues/4785), so the position half predates Quattro.

One piece did get fixed. [PR #7581](https://github.com/omacom/omarchy/pull/7581) by fuchsblau merged on 2026-08-22 and shipped in [4.0.1](/releases/v4.0.1/) as "Leave an auto-scaled internal panel alone in clamshell recovery". It makes the recovery step return early when the configured scale is not a number, which is the default `"auto"`, and PedroMiguelInacio's [#6909](https://github.com/omacom/omarchy/issues/6909) was closed minutes after it merged. The persistence PRs have not landed: [#7437](https://github.com/omacom/omarchy/pull/7437) was still open on 2026-09-16, and [#8145](https://github.com/omacom/omarchy/pull/8145) was closed unmerged on 2026-09-17 and folded into [#12263](https://github.com/omacom/omarchy/pull/12263), which is open.

## If that did not work

**You keyed a monitor by description.** `desc:` is valid Hyprland syntax and Hyprland honours it, but the clamshell script does not. Give the internal panel a connector-name rule instead. mkelk's reasoning in [#7084](https://github.com/omacom/omarchy/issues/7084) is that `eDP` numbering holds across hotplug and it is DP connectors that renumber, which is what `desc:` exists to survive. So use `desc:` for external screens if you need it and keep the internal panel literal.

**You generated the rules with a Lua loop.** In a comment on [#8103](https://github.com/omacom/omarchy/issues/8103), gfk reported that this makes things worse, because the shell parser finds no literal line and falls back to the catch-all on every monitor event and idle-wake. Keep at least the internal panel's rule as one plain line.

**Your `monitors.lua` is a symlink into a dotfiles repo.** FCygan reported in [#7625](https://github.com/omacom/omarchy/issues/7625) that `sed -i` without `--follow-symlinks` replaces the symlink with a regular file the first time a scale button is pressed, so the repo quietly stops driving the config. Check with `ls -l ~/.config/hypr/monitors.lua` after any scale change.

**Rotation comes back wrong after a reboot.** YuseiRun reported in [#7066](https://github.com/omacom/omarchy/issues/7066) that a `transform` value in the config is present but not applied until you change it and change it back. That report is one machine, with `transform` set on the catch-all rule and the scale quoted as a string, and nobody else has confirmed it, so treat it as thin evidence rather than a confirmed pattern.

**Nothing responds to the keyboard after your edit.** A Lua error in `monitors.lua` aborts evaluation before the bindings load. See [the nil global o page](/fix/hyprland-lua-attempt-to-index-nil-global-o/), and recover the shipped file with `omarchy-refresh-config hypr/monitors.lua`, which backs your version up first.

**The screen comes back blank rather than misplaced.** That is a different failure. Try [suspend and resume](/fix/suspend-wont-resume-s2idle/) or [the dock notes](/hardware/thunderbolt-dock/).

## Related

- [Monitor layout lost after Quattro](/fix/monitors-conf-replaced-by-monitors-lua/)
- [Fractional scaling looks blurry or huge](/fix/fractional-scaling-blurry-or-huge-apps/)
- [Multi-monitor hardware notes](/hardware/multi-monitor/)
- [Hyprland conf to Lua migration](/reference/hyprland-conf-to-lua-migration/)
- [Omarchy manual: Monitors](https://omarchy.org/manual/monitors/)

## Sources

- [Issue #6673: [Quattro] Display panel scale is per-monitor at runtime but persists globally, rescaling other monitors on reload](https://github.com/omacom/omarchy/issues/6673)
- [Issue #7084: Clamshell watcher ignores desc: rules for the internal panel and overwrites its scale every 2s](https://github.com/omacom/omarchy/issues/7084)
- [Issue #7326: Internal monitor position cannot be made to stick: clamshell watcher and Display panel scaling both re-apply position = "auto"](https://github.com/omacom/omarchy/issues/7326)
- [Issue #8103: omarchy-hyprland-monitor-scaling doesn't persist scale on outputs with explicit per-monitor rules in monitors.lua](https://github.com/omacom/omarchy/issues/8103)
- [Issue #7625: omarchy-hyprland-monitor-scaling: scale reverts instantly, monitors get rearranged, and sed -i destroys a symlinked monitors.lua](https://github.com/omacom/omarchy/issues/7625)
- [Issue #7242: omarchy hyprland monitor scaling reverts instantly when monitors.lua has per-monitor rules](https://github.com/omacom/omarchy/issues/7242)
- [Issue #6909: [Quattro] eDP-1 monitor scaling forces 2x reset on mixed-monitor array with Quickshell navbar](https://github.com/omacom/omarchy/issues/6909)
- [Issue #7066: Omarchy Quatro: Not Honoring Transform in Monitor Config](https://github.com/omacom/omarchy/issues/7066)
- [Issue #4785: Omarchy 3.4.0: Monitor scale cycling breaks layout](https://github.com/omacom/omarchy/issues/4785)
- [Discussion #8213: Update monitor configuration defaults and docs to use relative positioning (auto) to prevent cursor trapping](https://github.com/omacom/omarchy/discussions/8213)
- [PR #7581: Leave an auto-scaled internal panel alone in clamshell recovery](https://github.com/omacom/omarchy/pull/7581)
- [PR #7437: Persist monitor scaling to explicit per-monitor entries](https://github.com/omacom/omarchy/pull/7437)
- [PR #8145: Persist monitor scaling onto named per-output rules](https://github.com/omacom/omarchy/pull/8145)
- [PR #12263: Monitor: report Hyprland scale and persist named outputs](https://github.com/omacom/omarchy/pull/12263)
- [Omarchy manual: Monitors](https://omarchy.org/manual/monitors/)
