openai / openai/codex

`threads.title`, `.preview` and `.first_user_message` each store the full first user message — 3 copies, up to 1 MB each, per thread

Open
#45,367 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

app-server bug performance
Dominant language
Rust
Stars
125k
Forks
19.4k
PR merge metrics
PR metrics pending

Description

Summary

In the codex app-server thread store (codex-home/state_5.sqlite, table threads), the columns
title, preview and first_user_message hold identical, full copies of the thread's opening
user message
. When a host attaches conversation history to that first message (OpenClaw does,
~400 KB per new thread), every thread costs ~1.2 MB of metadata before a second message exists.

Measured (codex 0.153.4, Windows, two agents)

Counts below were taken at different hours on 2026-09-13/14 while the store grew from 817 to 825 threads (~25/day); each count was 100% of the rows present at that moment.

  • 823 of 823 threads (count at 2026-09-13 19:1x; the store grows ~25 threads/day): title == preview == first_user_message byte-for-byte
  • largest title: 1,047,902 chars; 529 of 817 titles > 500,000 chars
  • the three columns: 1,787 MB of a 1,812 MB file; second agent 1,770 MB
  • SELECT id,title,preview,first_user_message FROM threads (819 rows at 15:2x the same day): 21.8 s; with title cut
    to 200 chars: 3.1 s
  • ceiling tracks the host's paste size, not a codex limit: max title/day stepped ~930 K (mid-Aug) →
    ~415 K (from 31 Aug) as the host reduced its paste

What appears to be happening

title and preview do not look derived — they look like an unbounded fallback to the first user
message. Evidence: the protocol-level name field is NULL in 825 of 825 threads (no title was ever
generated), and title == preview == first_user_message byte-for-byte in every row. A host consumer
(OpenClaw) clips preview to 500 chars before display, which suggests these fields were intended to be
short. So the defect is not that three fields exist — that is a reasonable listing denormalization —
but that the fallback copies the full message with no bound.

Why it matters

Thread listing reads the table; at ~800 threads the read is ~20+ s and everything that waits on it
(a host's message delivery, in our case) queues behind it. The host that consumes preview clips it
to 500 chars before display, so >99.9% of the stored bytes are never used by anything we can find.
title and first_user_message are not exposed over the app-server protocol at all.

Ask

Store title and preview as bounded derivations (a few hundred chars), and either bound
first_user_message or document that it is the canonical full copy so hosts can plan for it.

Repro

Any host that sends a large first user message; then sqlite3 state_5.sqlite "SELECT LENGTH(title), LENGTH(preview), LENGTH(first_user_message) FROM threads ORDER BY 1 DESC LIMIT 5".

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in the codex app-server thread store and inspect how the threads table populates title, preview, and first_user_message; reproduce the issue with the provided sqlite3 LENGTH query. Determine which field remains the canonical full copy and apply a bounded policy to the listing fields, or document the full-copy behavior. Done means large first messages no longer make title and preview unbounded while thread listing remains usable.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust, sqlite
Domain
databases
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.