`threads.title`, `.preview` and `.first_user_message` each store the full first user message — 3 copies, up to 1 MB each, per thread
Nobody has claimed this yet.
- 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_messagebyte-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; withtitlecut
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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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