Proof Portal
Omarchy
ProbeLabs65 findings · 95 requirementsA 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.
Speed test panel leaks 8 unbounded curl workers when dismissed mid-run — saturates the link until reboot (152 GB in 76 min)
Reported by LAVA-3D · filed Sep 12, 2026Closed upstream on 2026-09-21 by bjarneo via an automated message, as a duplicate of #8515 (R-1389). GitHub close reason: completed.
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?
- Not yet mapped to any requirement or component.
- 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. No decision has been recorded yet.
Original report
Summary
Dismissing the Speed Test panel while a measurement is running leaves its 8 traffic_worker subshells alive. They keep looping curl -o /dev/null against Netflix OCA nodes forever, at full line rate, with no byte cap and no timeout.
On my connection this silently downloaded 152 GB in 76 minutes at a sustained 496 Mbps. Every byte goes to /dev/null. Nothing in the UI indicates it is still running, and it survives until reboot or a manual kill. On a metered or capped connection this would be expensive.
Steps to reproduce
- Open the Ethernet (network) panel from the bar
- Click Speed Test
- Wait until the test actually starts (dials begin moving)
- Press Escape
The overlay closes, but the workers keep running:
$ ps -eo pid,ppid,etimes,args | grep '[o]marchy-network-speedtest'
155244 1015 60 /bin/bash /usr/share/omarchy/bin/omarchy-network-speedtest down
155245 1015 60 /bin/bash /usr/share/omarchy/bin/omarchy-network-speedtest down
155246 1015 60 /bin/bash /usr/share/omarchy/bin/omarchy-network-speedtest down
155249 1015 60 /bin/bash /usr/share/omarchy/bin/omarchy-network-speedtest down
155251 1015 60 /bin/bash /usr/share/omarchy/bin/omarchy-network-speedtest down
155253 1015 60 /bin/bash /usr/share/omarchy/bin/omarchy-network-speedtest down
155255 1015 60 /bin/bash /usr/share/omarchy/bin/omarchy-network-speedtest downppid=1015 is systemd --user — the parent script is gone and the workers have been reparented. They sit at 496 Mbps indefinitely. Repeating the steps stacks another 8 on top (I accumulated 21 across three dismissals).
Expected: dismissing the panel stops all traffic.
Root cause
Two independent facts combine.
1. The workers are unbounded and rely entirely on the parent's EXIT trap.
bin/omarchy-network-speedtest forks parallel=8 background subshells:
traffic_worker() {
...
while true; do
url=${urls[$((idx % url_count))]}
curl -fsS -o /dev/null "$url" 2>/dev/null || return
idx=$((idx + 1))
done
}There is no --max-time, no byte cap, and no overall deadline. The only thing that ever stops them is trap cleanup EXIT in the parent.
2. That trap only survives SIGTERM, and the parent is being SIGKILLed.
Controlled test — start the script, let it settle, signal the parent, count survivors:
| Signal to parent | Before | After | Survivors |
|---|---|---|---|
| SIGTERM | 9 | 0 | trap runs, workers reaped correctly |
| SIGKILL | 9 | 8 | all reparented to ppid=1015 |
The SIGKILL signature matches the production leak exactly (8 workers, ppid=1015).
On dismiss, plugins/panels/speedtest/Panel.qml close() does set speedTestProc.running = false, which sends SIGTERM — so the QML intent is correct. But the parent is blocked in sleep 1 inside its sampling loop:
while any_alive; do
sleep 1
...
donebash defers trap handling until the foreground sleep 1 returns, so the EXIT trap needs up to ~1s to fire. The panel is torn down and the Process object destroyed in that window, and the kill that follows lands before cleanup ever runs. The 8 workers are orphaned.
Note this makes the leak a race, which matches it being intermittent: dismissing very early or very late behaves differently from dismissing mid-phase.
Suggested fix
The durable fix is to make the workers self-limiting so they cannot outlive the parent regardless of which signal it receives. Relying on a trap in the parent is inherently fragile against SIGKILL.
Something like:
script_pid=$$ # capture before forking
traffic_worker() {
local urls=("$@")
...
while kill -0 "$script_pid" 2>/dev/null; do
url=${urls[$((idx % url_count))]}
curl --max-time 15 -fsS -o /dev/null "$url" 2>/dev/null || return
idx=$((idx + 1))
done
}Two cheap belt-and-braces additions:
curl --max-timeso a single request can never run unbounded- an overall wall-clock deadline in the script (the panel only ever needs ~5s per phase, so even a 30s cap would be generous) — this also protects any other caller, and the
updirection has the same structure
Workaround
For anyone hitting this before a fix lands, a user-level systemd timer that reaps speedtest processes older than 60s (legitimate panel runs last ~10s) stops the bleeding. Verified: a 2s run is spared, an orphaned one is killed within a minute.
System
| Omarchy | 4.0.3-1 |
| Kernel | 7.2.3-arch1-3 |
| CPU | AMD Ryzen 5 3600 6-Core |
| GPU | NVIDIA GeForce RTX 4070 Ti (AD104) |
| Hyprland | 0.56.2 |
| quickshell | 0.3.1-1 |
(omarchy debug --no-sudo --print is referenced in the docs but omarchy debug is not a recognised command on 4.0.3 — details gathered manually.)
View raw text
## Summary
Dismissing the Speed Test panel while a measurement is running leaves its 8 `traffic_worker` subshells alive. They keep looping `curl -o /dev/null` against Netflix OCA nodes **forever**, at full line rate, with no byte cap and no timeout.
On my connection this silently downloaded **152 GB in 76 minutes** at a sustained 496 Mbps. Every byte goes to `/dev/null`. Nothing in the UI indicates it is still running, and it survives until reboot or a manual kill. On a metered or capped connection this would be expensive.
## Steps to reproduce
1. Open the Ethernet (network) panel from the bar
2. Click **Speed Test**
3. Wait until the test actually starts (dials begin moving)
4. Press **Escape**
The overlay closes, but the workers keep running:
```
$ ps -eo pid,ppid,etimes,args | grep '[o]marchy-network-speedtest'
155244 1015 60 /bin/bash /usr/share/omarchy/bin/omarchy-network-speedtest down
155245 1015 60 /bin/bash /usr/share/omarchy/bin/omarchy-network-speedtest down
155246 1015 60 /bin/bash /usr/share/omarchy/bin/omarchy-network-speedtest down
155249 1015 60 /bin/bash /usr/share/omarchy/bin/omarchy-network-speedtest down
155251 1015 60 /bin/bash /usr/share/omarchy/bin/omarchy-network-speedtest down
155253 1015 60 /bin/bash /usr/share/omarchy/bin/omarchy-network-speedtest down
155255 1015 60 /bin/bash /usr/share/omarchy/bin/omarchy-network-speedtest down
```
`ppid=1015` is `systemd --user` — the parent script is gone and the workers have been reparented. They sit at 496 Mbps indefinitely. Repeating the steps stacks another 8 on top (I accumulated 21 across three dismissals).
**Expected:** dismissing the panel stops all traffic.
## Root cause
Two independent facts combine.
**1. The workers are unbounded and rely entirely on the parent's EXIT trap.**
`bin/omarchy-network-speedtest` forks `parallel=8` background subshells:
```bash
traffic_worker() {
...
while true; do
url=${urls[$((idx % url_count))]}
curl -fsS -o /dev/null "$url" 2>/dev/null || return
idx=$((idx + 1))
done
}
```
There is no `--max-time`, no byte cap, and no overall deadline. The only thing that ever stops them is `trap cleanup EXIT` in the parent.
**2. That trap only survives SIGTERM, and the parent is being SIGKILLed.**
Controlled test — start the script, let it settle, signal the parent, count survivors:
| Signal to parent | Before | After | Survivors |
|---|---|---|---|
| SIGTERM | 9 | **0** | trap runs, workers reaped correctly |
| SIGKILL | 9 | **8** | all reparented to `ppid=1015` |
The SIGKILL signature matches the production leak exactly (8 workers, `ppid=1015`).
On dismiss, `plugins/panels/speedtest/Panel.qml` `close()` does set `speedTestProc.running = false`, which sends SIGTERM — so the QML intent is correct. But the parent is blocked in `sleep 1` inside its sampling loop:
```bash
while any_alive; do
sleep 1
...
done
```
bash defers trap handling until the foreground `sleep 1` returns, so the EXIT trap needs up to ~1s to fire. The panel is torn down and the `Process` object destroyed in that window, and the kill that follows lands before `cleanup` ever runs. The 8 workers are orphaned.
Note this makes the leak a race, which matches it being intermittent: dismissing very early or very late behaves differently from dismissing mid-phase.
## Suggested fix
The durable fix is to make the workers self-limiting so they cannot outlive the parent regardless of which signal it receives. Relying on a trap in the parent is inherently fragile against SIGKILL.
Something like:
```bash
script_pid=$$ # capture before forking
traffic_worker() {
local urls=("$@")
...
while kill -0 "$script_pid" 2>/dev/null; do
url=${urls[$((idx % url_count))]}
curl --max-time 15 -fsS -o /dev/null "$url" 2>/dev/null || return
idx=$((idx + 1))
done
}
```
Two cheap belt-and-braces additions:
- `curl --max-time` so a single request can never run unbounded
- an overall wall-clock deadline in the script (the panel only ever needs ~5s per phase, so even a 30s cap would be generous) — this also protects any other caller, and the `up` direction has the same structure
## Workaround
For anyone hitting this before a fix lands, a user-level systemd timer that reaps speedtest processes older than 60s (legitimate panel runs last ~10s) stops the bleeding. Verified: a 2s run is spared, an orphaned one is killed within a minute.
## System
| | |
|---|---|
| Omarchy | 4.0.3-1 |
| Kernel | 7.2.3-arch1-3 |
| CPU | AMD Ryzen 5 3600 6-Core |
| GPU | NVIDIA GeForce RTX 4070 Ti (AD104) |
| Hyprland | 0.56.2 |
| quickshell | 0.3.1-1 |
(`omarchy debug --no-sudo --print` is referenced in the docs but `omarchy debug` is not a recognised command on 4.0.3 — details gathered manually.)
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
Not linked to any known issue, other report or change yet.
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.
No decision has been recorded yet.