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-427GitHub issuePublicFixed

Menu extension overrides wipe unspecified fields (icon, label, when) instead of merging

Reported by jenseralmeida · filed Sep 17, 2026Closed upstream on 2026-10-12 by the bot omarchybot, as a duplicate (target not recorded).

Decided: Duplicate — decided by agents · verified 7 days ago.

R-711 is the earliest report of this defect — filed Sep 10, 2026, open upstream. This report (filed Sep 17, 2026) describes the same defect, so it is folded under it in the inbox.

The original is tracked as known issuenormalize-before-merge wipes unspecified defaults: a label-only user entry erases action/aliases/guards KI-MENU-MERGE-OVERRIDE-WIPES-DEFAULTS

Decided by agents · verified· Oct 5, 2026

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?
AreaIn 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?
Decided by agents and verified by a second, independent agent — see the decision.

Original report

Menu extension overrides wipe unspecified fields (icon, label, when) instead of mergingrevision 1 · captured 2026-09-28 21:23 UTC via poll

What happened

~/.config/omarchy/extensions/omarchy-menu.jsonc documents that reusing an existing menu id merges into the stock entry:

// root menu. Reuse an existing id to override/extend it. // Example: replace the default About action by reusing the same id. Existing // fields are kept unless overridden.

In practice, redefining an existing id replaces every field. Overriding only action silently drops the entry's icon, label, when, checked, description, aliases, target and provider.

Overriding just the logout action:

"system.logout": {"action":"..."},

renders the row with no icon and the literal id system.logout as its label, instead of 󰍃 / "Logout".

Expected behavior

Per the documented contract, unspecified fields should be inherited from the stock entry.

Root cause

shell/plugins/menu/MenuModel.js. mergeMenuSources() (line 65) does implement the documented field-wise merge:

var merged = {}
for (var k in prior) merged[k] = prior[k]
for (var k2 in entry) merged[k2] = entry[k2]

But each source is normalized before it reaches that merge. parseMenuJsonc() (line 41) calls normalizeItem() (line 13) on every raw entry:

icon: value.icon || "",
label: value.label || id,
...
when: value.when || "",

So by merge time the user entry is a fully-populated object where every unspecified field is present as "" (or the id, for label). for (var k2 in entry) then copies those empty values over the stock ones — "absent" and "explicitly empty" are indistinguishable, and nothing is ever left un-overridden. label: value.label || id is exactly why the row falls back to showing system.logout.

Why it matters beyond cosmetics

when is wiped the same way. A user overriding only the action of system.hibernate:

"system.hibernate": {"action":"..."},

loses "when":"omarchy-hibernation-available", so the row shows unconditionally on machines where hibernation isn't available. Same hazard for system.suspend's when and for any checked condition.

Steps to reproduce

  1. Add "system.logout": {"action":"omarchy-system-logout"}, to ~/.config/omarchy/extensions/omarchy-menu.jsonc
  2. omarchy menu refresh
  3. Open the menu — the Logout row now has no icon and is labelled system.logout

Suggested fix

Merge the raw JSONC values first and normalize once at the end, rather than normalizing per source. Either have parseMenuJsonc() return raw {id, value} pairs and run normalizeItem() after mergeMenuSources(), or have normalizeItem() only set keys actually present in raw and apply the defaults in a final pass.

System details

  • Omarchy version: 4.0.4-1
View raw text
## What happened

`~/.config/omarchy/extensions/omarchy-menu.jsonc` documents that reusing an existing menu id merges into the stock entry:

> `// root menu. Reuse an existing id to override/extend it.`
> `// Example: replace the default About action by reusing the same id. Existing`
> `// fields are kept unless overridden.`

In practice, redefining an existing id **replaces** every field. Overriding only `action` silently drops the entry's `icon`, `label`, `when`, `checked`, `description`, `aliases`, `target` and `provider`.

Overriding just the logout action:

```jsonc
"system.logout": {"action":"..."},
```

renders the row with no icon and the literal id `system.logout` as its label, instead of 󰍃 / "Logout".

## Expected behavior

Per the documented contract, unspecified fields should be inherited from the stock entry.

## Root cause

`shell/plugins/menu/MenuModel.js`. `mergeMenuSources()` (line 65) does implement the documented field-wise merge:

```js
var merged = {}
for (var k in prior) merged[k] = prior[k]
for (var k2 in entry) merged[k2] = entry[k2]
```

But each source is normalized *before* it reaches that merge. `parseMenuJsonc()` (line 41) calls `normalizeItem()` (line 13) on every raw entry:

```js
icon: value.icon || "",
label: value.label || id,
...
when: value.when || "",
```

So by merge time the user entry is a fully-populated object where every unspecified field is present as `""` (or the id, for `label`). `for (var k2 in entry)` then copies those empty values over the stock ones — "absent" and "explicitly empty" are indistinguishable, and nothing is ever left un-overridden. `label: value.label || id` is exactly why the row falls back to showing `system.logout`.

## Why it matters beyond cosmetics

`when` is wiped the same way. A user overriding only the action of `system.hibernate`:

```jsonc
"system.hibernate": {"action":"..."},
```

loses `"when":"omarchy-hibernation-available"`, so the row shows unconditionally on machines where hibernation isn't available. Same hazard for `system.suspend`'s `when` and for any `checked` condition.

## Steps to reproduce

1. Add `"system.logout": {"action":"omarchy-system-logout"},` to `~/.config/omarchy/extensions/omarchy-menu.jsonc`
2. `omarchy menu refresh`
3. Open the menu — the Logout row now has no icon and is labelled `system.logout`

## Suggested fix

Merge the raw JSONC values first and normalize once at the end, rather than normalizing per source. Either have `parseMenuJsonc()` return raw `{id, value}` pairs and run `normalizeItem()` after `mergeMenuSources()`, or have `normalizeItem()` only set keys actually present in `raw` and apply the defaults in a final pass.

## System details

- Omarchy version: 4.0.4-1

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

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.

  • DuplicateR-711Decided by agents · verified · 2026-10-05 05:17 UTC