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.
ssh wrapper leaves the alternate screen after every ssh, scrambling the terminal
Reported by AronBakes · filed Sep 16, 2026 · open at source
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?
- AreaOutside the audited area· 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
System details
AMD Raphael iGPU + RTX 5070, Omarchy 4.0.0.alpha, bash 5.x, foot and Herdr 0.8.2
What's wrong?
_ssh_disarm in default/bash/fns/ssh-reconnect runs after every ssh
invocation, and it ends with \e[?1049l — leave the alternate screen.
Leaving the alternate screen also restores the cursor, the way DECRC does. When
nothing ever entered the alternate screen there is no saved cursor to restore,
and DEC semantics put the cursor at home. The shell then draws its next prompt
and output over lines already on screen.
No remote host is needed — ssh -V is enough, and so is an ssh to a name that
does not resolve.
Reproduce
clear
for i in 1 2 3 4 5; do echo "BEFORE-$i"; done
ssh -V
for i in 1 2 3; do echo "AFTER-$i"; doneExpected: AFTER-1..3 below BEFORE-1..5.
Actual: the cursor is back at the top, so AFTER-1 lands on BEFORE-1. SinceBEFORE-1 is one character longer, the row reads AFTER-11, then AFTER-22,AFTER-33, with BEFORE-4/BEFORE-5 still below — the overlapping text that
gets cleared by hand.
Isolating it
Bypassing the wrapper is clean, so it is the wrapper and not ssh:
command ssh -V # clean
ssh -V # scrambledAnd within the disarm, it is specifically 1049l:
printf '\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?1004l\e[?25h' # clean
printf '\e[?1049l' # scrambledWhere it shows up
| Terminal | Result |
|---|---|
| foot | not affected — ignores the reset when not in the alternate screen |
| tmux | not affected — same |
| Herdr | affected — applies the reset, cursor goes home |
Herdr is one of the environments this wrapper's own comment names ("A remote
tmux, herdr, or editor arms terminal modes over the SSH pipe"), so the cleanup
misfires in an environment it was written for. Emulators that ignore the reset
are being lenient — restoring the cursor with nothing saved is the documented
DEC behaviour, so the unconditional send is the fragile part.
Suggested fix
Keep the mouse-tracking (1000/1002/1003/1006), focus-reporting (1004) and cursor
(25h) resets unconditional — none of them can move the cursor. Send 1049l only
for the case the wrapper exists for: a session that was established and then
dropped. That is the same rc == 255 plus elapsed-time pair the reconnect path
already uses to tell a real drop from a connect or auth failure; a fast 255
never had a session and cannot have armed anything.
PR to follow.
View raw text
### System details
AMD Raphael iGPU + RTX 5070, Omarchy 4.0.0.alpha, bash 5.x, foot and Herdr 0.8.2
### What's wrong?
`_ssh_disarm` in `default/bash/fns/ssh-reconnect` runs after **every** `ssh`
invocation, and it ends with `\e[?1049l` — leave the alternate screen.
Leaving the alternate screen also restores the cursor, the way DECRC does. When
nothing ever entered the alternate screen there is no saved cursor to restore,
and DEC semantics put the cursor at home. The shell then draws its next prompt
and output over lines already on screen.
No remote host is needed — `ssh -V` is enough, and so is an ssh to a name that
does not resolve.
**Reproduce**
```bash
clear
for i in 1 2 3 4 5; do echo "BEFORE-$i"; done
ssh -V
for i in 1 2 3; do echo "AFTER-$i"; done
```
Expected: `AFTER-1..3` below `BEFORE-1..5`.
Actual: the cursor is back at the top, so `AFTER-1` lands on `BEFORE-1`. Since
`BEFORE-1` is one character longer, the row reads `AFTER-11`, then `AFTER-22`,
`AFTER-33`, with `BEFORE-4`/`BEFORE-5` still below — the overlapping text that
gets cleared by hand.
**Isolating it**
Bypassing the wrapper is clean, so it is the wrapper and not ssh:
```bash
command ssh -V # clean
ssh -V # scrambled
```
And within the disarm, it is specifically `1049l`:
```bash
printf '\e[?1000l\e[?1002l\e[?1003l\e[?1006l\e[?1004l\e[?25h' # clean
printf '\e[?1049l' # scrambled
```
**Where it shows up**
| Terminal | Result |
| --- | --- |
| foot | not affected — ignores the reset when not in the alternate screen |
| tmux | not affected — same |
| Herdr | **affected** — applies the reset, cursor goes home |
Herdr is one of the environments this wrapper's own comment names ("A remote
tmux, herdr, or editor arms terminal modes over the SSH pipe"), so the cleanup
misfires in an environment it was written for. Emulators that ignore the reset
are being lenient — restoring the cursor with nothing saved is the documented
DEC behaviour, so the unconditional send is the fragile part.
**Suggested fix**
Keep the mouse-tracking (1000/1002/1003/1006), focus-reporting (1004) and cursor
(25h) resets unconditional — none of them can move the cursor. Send `1049l` only
for the case the wrapper exists for: a session that was established and then
dropped. That is the same `rc == 255` plus elapsed-time pair the reconnect path
already uses to tell a real drop from a connect or auth failure; a fast 255
never had a session and cannot have armed anything.
PR to follow.
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.
- Out of scopeDecided by agents · verified · 2026-10-09 14:03 UTC