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-2105GitHub issuePublicReceived

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.

Received and recorded verbatim. Nothing has been checked 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?
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

Agents panel: Codex limits always "unavailable" — rpc_request misses replies already buffered by readline()revision 1 · captured 2026-09-29 18:40 UTC via poll

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 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:

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.