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.
Agents panel: Codex limits always "unavailable" — rpc_request misses replies already buffered by readline()
Reported by Andrelyn12035 · filed Sep 29, 2026Closed upstream on 2026-10-01 by its own reporter Andrelyn12035, as a duplicate of #13571 (R-5127). GitHub close reason: duplicate.
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
System
- Omarchy 4.0.4-1
- CPU: 13th Gen Intel Core i7-13700H
- GPU: Intel Raptor Lake-P (Iris Xe)
- codex-cli 0.156.1 (logged in with ChatGPT, Plus plan)
What happens
The agents panel shows Codex usage from local stats, but never its plan or limits.~/.local/state/omarchy/agents/usage/codex.json always ends with:
"limits": [],
"tierLabel": "",
"usageStatusText": "Codex limits unavailable",
"authHelpText": "account/read""account/read" is the TimeoutError(method) raised by rpc_request inbin/omarchy-agent-usage-codex.
Expected
Plan and the 5-hour / weekly limits, which the app-server returns fine:account/read answers in ~0.5 s and account/rateLimits/read in ~0.8 s.
Steps to reproduce
- Codex 0.156.1 logged in with ChatGPT.
omarchy agent usage update --force codexjq '{usageStatusText, authHelpText, limits}' ~/.local/state/omarchy/agents/usage/codex.json
→ Codex limits unavailable / account/read / [], every run.
Cause
Right after initialized, codex 0.156.1 emits two notifications
(remoteControl/status/changed, account/updated). They arrive in the same
chunk as the reply to account/read.
rpc_request waits with select.select([proc.stdout], ...) and then callsproc.stdout.readline(). proc.stdout is a buffered text stream: the firstreadline() pulls the whole chunk into Python's buffer and returns one line.
The reply is now in that buffer, not in the pipe, so select() never reports
the fd readable again and the loop spins until the 4 s timeout. The exception
also skips account/rateLimits/read, so no limits are recorded.
Traced from inside the collector:
0.1s <- {"id":1,"result":{...}} (initialize)
0.5s <- {"method":"remoteControl/status/changed",...}
0.5s <- {"method":"account/updated","params":{"authMode":"chatgpt","planType":"plus"}}
total 4.3s -> "Codex limits unavailable", "account/read"With the only change being to skip select() (blocking readline()), the same
code returns in 0.9 s with tierLabel: "plus" and both limit windows.
The code is unchanged on master (select.select([proc.stdout], [], [], 0.25)
in rpc_request), so this affects any codex version that sends notifications
before a reply.
Suggested fix
Don't mix select() with a buffered reader. For example, read stdout on a
background thread into a queue and wait on the queue with the timeout:
import queue, threading
def start_reader(proc):
lines = queue.Queue()
def pump():
for line in proc.stdout:
lines.put(line)
lines.put(None)
threading.Thread(target=pump, daemon=True).start()
return lines
def rpc_request(proc, lines, request_id, method, params=None, timeout=8):
payload = {"id": request_id, "method": method, "params": params or {}}
proc.stdin.write(json.dumps(payload) + "\n")
proc.stdin.flush()
deadline = time.time() + timeout
while (remaining := deadline - time.time()) > 0:
try:
line = lines.get(timeout=remaining)
except queue.Empty:
break
if line is None:
break
try:
message = json.loads(line)
except Exception:
continue
if message.get("id") == request_id:
return message
raise TimeoutError(method)(fetch_codex_rpc creates lines = start_reader(proc) once after Popen and
passes it to each call.) Alternatively, open the pipe unbuffered in binary mode
and split lines from os.read() yourself.
View raw text
## System
- Omarchy 4.0.4-1
- CPU: 13th Gen Intel Core i7-13700H
- GPU: Intel Raptor Lake-P (Iris Xe)
- codex-cli 0.156.1 (logged in with ChatGPT, Plus plan)
## What happens
The agents panel shows Codex usage from local stats, but never its plan or limits.
`~/.local/state/omarchy/agents/usage/codex.json` always ends with:
```json
"limits": [],
"tierLabel": "",
"usageStatusText": "Codex limits unavailable",
"authHelpText": "account/read"
```
`"account/read"` is the `TimeoutError(method)` raised by `rpc_request` in
`bin/omarchy-agent-usage-codex`.
## Expected
Plan and the 5-hour / weekly limits, which the app-server returns fine:
`account/read` answers in ~0.5 s and `account/rateLimits/read` in ~0.8 s.
## Steps to reproduce
1. Codex 0.156.1 logged in with ChatGPT.
2. `omarchy agent usage update --force codex`
3. `jq '{usageStatusText, authHelpText, limits}' ~/.local/state/omarchy/agents/usage/codex.json`
→ `Codex limits unavailable` / `account/read` / `[]`, every run.
## Cause
Right after `initialized`, codex 0.156.1 emits two notifications
(`remoteControl/status/changed`, `account/updated`). They arrive in the same
chunk as the reply to `account/read`.
`rpc_request` waits with `select.select([proc.stdout], ...)` and then calls
`proc.stdout.readline()`. `proc.stdout` is a buffered text stream: the first
`readline()` pulls the whole chunk into Python's buffer and returns one line.
The reply is now in that buffer, not in the pipe, so `select()` never reports
the fd readable again and the loop spins until the 4 s timeout. The exception
also skips `account/rateLimits/read`, so no limits are recorded.
Traced from inside the collector:
```
0.1s <- {"id":1,"result":{...}} (initialize)
0.5s <- {"method":"remoteControl/status/changed",...}
0.5s <- {"method":"account/updated","params":{"authMode":"chatgpt","planType":"plus"}}
total 4.3s -> "Codex limits unavailable", "account/read"
```
With the only change being to skip `select()` (blocking `readline()`), the same
code returns in 0.9 s with `tierLabel: "plus"` and both limit windows.
The code is unchanged on `master` (`select.select([proc.stdout], [], [], 0.25)`
in `rpc_request`), so this affects any codex version that sends notifications
before a reply.
## Suggested fix
Don't mix `select()` with a buffered reader. For example, read stdout on a
background thread into a queue and wait on the queue with the timeout:
```python
import queue, threading
def start_reader(proc):
lines = queue.Queue()
def pump():
for line in proc.stdout:
lines.put(line)
lines.put(None)
threading.Thread(target=pump, daemon=True).start()
return lines
def rpc_request(proc, lines, request_id, method, params=None, timeout=8):
payload = {"id": request_id, "method": method, "params": params or {}}
proc.stdin.write(json.dumps(payload) + "\n")
proc.stdin.flush()
deadline = time.time() + timeout
while (remaining := deadline - time.time()) > 0:
try:
line = lines.get(timeout=remaining)
except queue.Empty:
break
if line is None:
break
try:
message = json.loads(line)
except Exception:
continue
if message.get("id") == request_id:
return message
raise TimeoutError(method)
```
(`fetch_codex_rpc` creates `lines = start_reader(proc)` once after `Popen` and
passes it to each call.) Alternatively, open the pipe unbuffered in binary mode
and split lines from `os.read()` yourself.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.