Proof Portal

Project overview

Omarchy

ProbeLabs65 findings · 95 requirements

A proof layer — requirements, tests and verified fixes — for two of Omarchy's subsystems: the application menu (launcher scripts, QML model, JSONC config, search and selection) and the lock screen (lock scripts, QML, PAM authentication). Scope is deliberately limited to those components of omacom/omarchy; the rest of the distribution is not covered.

Inbox
ReportR-1363GitHub issuePublicFixed

powerprofilesctl crashes (SIGABRT) when power-profiles-daemon is masked — hit on every power event with TLP

Reported by ogrt · filed Aug 27, 2026 · open at source

Partly in the audited area (menu) — decided by agents · verified. Not validated yet.

At a glance

1 · What is claimed?
Only the report text below — no validator has restated it as checkable claims yet.
2 · What does it affect?
AreaPartly in the audited areamenu· Decided by agents · verified
3 · Is it reproducible?
No validation has been run yet.
4 · What remains unclear?
  • Not validated yet: no claims, no reproducer.
5 · What happens next?
Our staff decide whether the report is valid. Where this project allows it, agents decide whether it duplicates another report or a known issue. So far: partly in the audited area (menu) — decided by agents · verified; matches no known issue — decided by agents · verified. The outcome (valid, duplicate, not a bug, …) is not decided yet. See the decision.

Original report

powerprofilesctl crashes (SIGABRT) when power-profiles-daemon is masked — hit on every power event with TLPrevision 1 · captured 2026-09-28 21:24 UTC via poll

Summary

powerprofilesctl (the Python CLI shipped with power-profiles-daemon) hard-crashes with SIGABRT when invoked while power-profiles-daemon.service is masked/inactive — e.g. on a TLP-managed setup, which is a common alternative to power-profiles-daemon on laptops. The crash is a Python interpreter-shutdown race (PyGILState_Ensure() called against an already-torn-down GIL from a GObject finalizer), not a clean error return, so it can't be handled by checking the exit code alone.

Several Omarchy Quickshell plugins call powerprofilesctl / omarchy-powerprofiles-set directly and hit this on every AC/battery transition and every time the power menu is opened.

Environment

  • Omarchy 4.0.0-1 (/usr/share/omarchy/version: 4.0.0.alpha)
  • power-profiles-daemon 0.30-1 (installed but masked — TLP manages power instead)
  • tlp 1.10.2-1
  • Kernel 7.1.8-arch1-3
  • Python 3.14.7
  • CPU: Intel Core i7-7820HQ

Reproduction

  1. Mask power-profiles-daemon.service (as recommended when using TLP instead): sudo systemctl mask power-profiles-daemon.service
  2. Trigger any of:
  • AC/battery power source change
  • Opening the Omarchy menu's power-profile submenu
  • Opening the Power bar widget/panel
  1. powerprofilesctl crashes with SIGABRT instead of failing cleanly.

Evidence

$ coredumpctl info 31765
           PID: 31765 (powerprofilesct)
           TID: 31805 (gdbus)
        Signal: 6 (ABRT) si_code: SI_TKILL
  Command Line: /bin/python3 /usr/bin/powerprofilesctl list
    Executable: /usr/bin/python3.14

Stack trace of thread 31805:
#0  raise (libc.so.6)
#1  abort (libc.so.6)
#2  n/a (libpython3.14.so.1.0 + 0x1642ca)
#3  _Py_FatalErrorFunc (libpython3.14.so.1.0)
#4  PyEval_RestoreThread (libpython3.14.so.1.0)
#5  PyGILState_Ensure (libpython3.14.so.1.0)
#6  n/a (_gi.cpython-314-x86_64-linux-gnu.so + 0x1156e)
#7  n/a (libglib-2.0.so.0)
#8  g_object_unref (libgobject-2.0.so.0)
#9  n/a (libgio-2.0.so.0)  [async D-Bus call teardown]
...

PyGILState_Ensure() is being called from a PyGObject callback fired during object finalization/interpreter shutdown, after the GIL has already been destroyed — this is a known class of PyGObject/GLib async-D-Bus race, not something specific to this machine. When power-profiles-daemon isn't running, powerprofilesctl's D-Bus proxy setup takes a different (failing/retrying) code path, which appears to be what triggers the race here; against a live daemon it doesn't reproduce.

Affected call sites (upstream Omarchy source)

  • shell/plugins/panels/power/Panel.qml — profile-set action shells out to omarchy-powerprofiles-set
  • shell/plugins/services/battery/Service.qml — calls omarchy-powerprofiles-set on every AC/battery transition
  • shell/plugins/menu/Menu.qml — power-profile submenu shells out to powerprofilesctl get/list to populate rows

None of these currently check whether power-profiles-daemon.service is actually active before invoking powerprofilesctl/omarchy-powerprofiles-set.

Proposed fix

Guard each call site with systemctl is-active --quiet power-profiles-daemon.service and skip the powerprofilesctl call entirely when it's masked/inactive, rather than letting it be invoked and crash. I have a working local patch that does this plus a best-effort fallback (deriving power-saver/balanced/performance from the CPU's own energy_performance_preference sysfs knob when no daemon is present) — happy to open a PR with it if that shape is welcome, or to split the guard-only fix from the EPP fallback if you'd rather review those separately.

Expected behavior

powerprofilesctl/omarchy-powerprofiles-set should not be invoked at all when power-profiles-daemon.service isn't active, so TLP-only setups don't hit this crash on every power event and menu open.

View raw text
## Summary

`powerprofilesctl` (the Python CLI shipped with `power-profiles-daemon`) hard-crashes with `SIGABRT` when invoked while `power-profiles-daemon.service` is masked/inactive — e.g. on a TLP-managed setup, which is a common alternative to `power-profiles-daemon` on laptops. The crash is a Python interpreter-shutdown race (`PyGILState_Ensure()` called against an already-torn-down GIL from a GObject finalizer), not a clean error return, so it can't be handled by checking the exit code alone.

Several Omarchy Quickshell plugins call `powerprofilesctl` / `omarchy-powerprofiles-set` directly and hit this on every AC/battery transition and every time the power menu is opened.

## Environment

- Omarchy 4.0.0-1 (`/usr/share/omarchy/version`: `4.0.0.alpha`)
- power-profiles-daemon 0.30-1 (installed but masked — TLP manages power instead)
- tlp 1.10.2-1
- Kernel 7.1.8-arch1-3
- Python 3.14.7
- CPU: Intel Core i7-7820HQ

## Reproduction

1. Mask `power-profiles-daemon.service` (as recommended when using TLP instead): `sudo systemctl mask power-profiles-daemon.service`
2. Trigger any of:
   - AC/battery power source change
   - Opening the Omarchy menu's power-profile submenu
   - Opening the Power bar widget/panel
3. `powerprofilesctl` crashes with SIGABRT instead of failing cleanly.

## Evidence

```
$ coredumpctl info 31765
           PID: 31765 (powerprofilesct)
           TID: 31805 (gdbus)
        Signal: 6 (ABRT) si_code: SI_TKILL
  Command Line: /bin/python3 /usr/bin/powerprofilesctl list
    Executable: /usr/bin/python3.14

Stack trace of thread 31805:
#0  raise (libc.so.6)
#1  abort (libc.so.6)
#2  n/a (libpython3.14.so.1.0 + 0x1642ca)
#3  _Py_FatalErrorFunc (libpython3.14.so.1.0)
#4  PyEval_RestoreThread (libpython3.14.so.1.0)
#5  PyGILState_Ensure (libpython3.14.so.1.0)
#6  n/a (_gi.cpython-314-x86_64-linux-gnu.so + 0x1156e)
#7  n/a (libglib-2.0.so.0)
#8  g_object_unref (libgobject-2.0.so.0)
#9  n/a (libgio-2.0.so.0)  [async D-Bus call teardown]
...
```

`PyGILState_Ensure()` is being called from a PyGObject callback fired during object finalization/interpreter shutdown, after the GIL has already been destroyed — this is a known class of PyGObject/GLib async-D-Bus race, not something specific to this machine. When `power-profiles-daemon` isn't running, `powerprofilesctl`'s D-Bus proxy setup takes a different (failing/retrying) code path, which appears to be what triggers the race here; against a live daemon it doesn't reproduce.

## Affected call sites (upstream Omarchy source)

- `shell/plugins/panels/power/Panel.qml` — profile-set action shells out to `omarchy-powerprofiles-set`
- `shell/plugins/services/battery/Service.qml` — calls `omarchy-powerprofiles-set` on every AC/battery transition
- `shell/plugins/menu/Menu.qml` — power-profile submenu shells out to `powerprofilesctl get`/`list` to populate rows

None of these currently check whether `power-profiles-daemon.service` is actually active before invoking `powerprofilesctl`/`omarchy-powerprofiles-set`.

## Proposed fix

Guard each call site with `systemctl is-active --quiet power-profiles-daemon.service` and skip the `powerprofilesctl` call entirely when it's masked/inactive, rather than letting it be invoked and crash. I have a working local patch that does this plus a best-effort fallback (deriving power-saver/balanced/performance from the CPU's own `energy_performance_preference` sysfs knob when no daemon is present) — happy to open a PR with it if that shape is welcome, or to split the guard-only fix from the EPP fallback if you'd rather review those separately.

## Expected behavior

`powerprofilesctl`/`omarchy-powerprofiles-set` should not be invoked at all when `power-profiles-daemon.service` isn't active, so TLP-only setups don't hit this crash on every power event and menu open.

Validation

Not checked yet. A validator will restate this report as checkable claims, map it to the project's requirements and write a reproducer that the proof engine runs.

Linked

  • Fixed bypr/8712· confirmed
  • Fixed bypr/12552· confirmed
  • Related PRpr/5716· confirmed

Decision

Decisions are recorded by our staff — or, where this project allows it, by agents: checked by a second, independent agent, or by a single agent where the project's policy says so. Any decision reached so far is shown below.

  • Decided by agents · verified: partly in the audited area (menu)
  • Decided by agents · verified: matches no known issue

The outcome (valid, duplicate, not a bug, …) is not decided yet.