[Bug]: Codex harness assistant replies do not mark Control UI sessions unread
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 390k
- Forks
- 82k
- PR merge metrics
- PR metrics pending
Description
Bug type
Behavior bug (incorrect session state without a crash)
Summary
Completed assistant replies from Codex agent-harness sessions do not make the session unread in the Control UI. The reply is durably present after the user's last read acknowledgement, but the session row remains unread: false.
Steps to reproduce
- Configure a session to use the Codex agent runtime.
- Open the session in the Control UI and send a message.
- Navigate away before the assistant reply completes.
- Wait for the completed reply.
- Inspect the session list.
Expected behavior
A completed user-facing assistant reply after lastReadAt advances unread-worthy activity and the session shows an unread badge.
Actual behavior
The assistant message is committed and the session updatedAt advances, but lastActivityAt remains absent. Since deriveSessionUnread() intentionally ignores updatedAt, the row stays read.
Environment
- OpenClaw 2026.9.2 (base release behavior; deployed build
83c63dchas no changes in the relevant unread/session-store files) - Fedora Linux, Node 26
- Control UI in browser
- Agent runtime: Codex
- Models observed:
openai/gpt-6-astra
Redacted evidence from two independent sessions
Session A (Telegram-backed Codex session)
lastReadAt: 2026-09-08 09:37:03 +02:00- durable assistant message: 2026-09-08 09:37:14 +02:00
updatedAt: after the assistant messagelastActivityAt: NULL- derived
unread: false
Session B (dashboard Codex session)
lastReadAt: 2026-09-07 14:28:52 +02:00- durable assistant message: 2026-09-07 14:29:15 +02:00
updatedAt: after the assistant messagelastActivityAt: NULL- derived
unread: false
No transcript text or private identifiers are included.
Source analysis
deriveSessionUnread() in src/gateway/session-utils-core.ts derives unread state only from:
- an explicit
markedUnreadAt, or max(lastInteractionAt, lastActivityAt) > (lastReadAt ?? createdAt)
This is consistent with the documented contract: lastActivityAt is the timestamp of the last completed unread-worthy agent run, while updatedAt is not an activity signal.
The canonical completion writer in src/agents/command/session-store.ts normally computes lastActivityAt, but its preserved-user-facing-state metadata patch explicitly omits it. The observed Codex harness completions advance updatedAt and commit the assistant transcript while leaving lastActivityAt NULL, so the unread derivation has no completion signal.
The same relevant logic is present on current origin/main at 8ac88a1a335aa5f6ec467e539f6bbce3df478d84.
Existing issues / PRs checked
- #100739 introduced the unread contract and states that completed activity should drive unread state.
- #128986 covers never-opened modern sessions.
- #129373 / #129386 cover preserving explicit manual unread markers.
- #134158 / #134160 cover optimistic clearing when a session opens.
- #126837 reported widespread NULL
last_activity_atfor retention tooling, but was closed as not planned because qualifying ordinary completions are expected to populate it. This report demonstrates a current qualifying Codex completion path where they do not.
Suggested repair
Ensure every completed, user-facing Codex/CLI harness turn persists lastActivityAt, even when model/accounting fields are intentionally preserved. Keep heartbeat, internal-event, and metadata-only paths excluded. Add an end-to-end regression that:
- records
lastReadAt, - completes a Codex harness assistant turn later,
- asserts
lastActivityAtadvances andsessions.listreturnsunread: true.
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 with src/gateway/session-utils-core.ts and src/agents/command/session-store.ts, then trace the Codex harness completion path and existing session unread tests. Verify that a completed user-facing Codex turn advances lastActivityAt without changing excluded heartbeat or metadata-only paths. Done means an end-to-end regression shows a later completed turn makes sessions.list return unread: true after lastReadAt.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- backend-api-design, testing-qa
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 74/100