authorgravatar for 71498452+StiensWout@users.noreply.github.comWout Stiens <71498452+StiensWout@users.noreply.github.com> 2026-07-17 23:24:52+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-07-17 23:24:52+02:00
log43e2aeab389199de115bb414f80db12b60cd85f4
tree8094cbfb572c97bd458d292e39dd64cde17da825
parentb655a9f183cc9f6b2fd54bf11ea28acb50d23cfd
signature Signed by PGP key B5690EEEBB952194

[codex] fix: tolerate invalid latest user message timestamps (#3521)

Co-authored-by: Codex <codex@openai.com> Co-authored-by: Julius Marminge <julius0216@outlook.com>

2 files changed, 75 insertions(+), 1 deletions(-)

packages/client-runtime/src/state/threadSort.test.ts created+71
...@@ -0,0 +1,71 @@
1import { describe, expect, it } from "vite-plus/test";
2
3import { sortThreads, type ThreadSortInput } from "./threadSort.ts";
4
5type TestThread = { readonly id: string } & ThreadSortInput;
6
7function makeThread(overrides: Partial<TestThread> = {}): TestThread {
8 return {
9 id: "thread-1",
10 createdAt: "2026-03-09T10:00:00.000Z",
11 updatedAt: "2026-03-09T10:00:00.000Z",
12 messages: [],
13 latestUserMessageAt: null,
14 ...overrides,
15 };
16}
17
18describe("sortThreads", () => {
19 it("falls back to updatedAt and createdAt when latestUserMessageAt is invalid and there are no messages", () => {
20 const sorted = sortThreads(
21 [
22 makeThread({
23 id: "thread-1",
24 latestUserMessageAt: "not-a-date",
25 createdAt: "2026-03-09T10:00:00.000Z",
26 updatedAt: "2026-03-09T10:05:00.000Z",
27 }),
28 makeThread({
29 id: "thread-2",
30 latestUserMessageAt: "still-not-a-date",
31 createdAt: "invalid-created-at",
32 updatedAt: "invalid-updated-at",
33 }),
34 makeThread({
35 id: "thread-3",
36 latestUserMessageAt: "invalid-latest-user-message-at",
37 createdAt: "2026-03-09T10:06:00.000Z",
38 updatedAt: "invalid-updated-at",
39 }),
40 ],
41 "updated_at",
42 );
43
44 expect(sorted.map((thread) => thread.id)).toEqual(["thread-3", "thread-1", "thread-2"]);
45 });
46
47 it("falls back to the latest valid user message when latestUserMessageAt is invalid", () => {
48 const sorted = sortThreads(
49 [
50 makeThread({
51 id: "thread-1",
52 latestUserMessageAt: "invalid-latest-user-message-at",
53 updatedAt: "2026-03-09T10:00:00.000Z",
54 messages: [
55 { role: "user", createdAt: "2026-03-09T10:05:00.000Z" },
56 { role: "assistant", createdAt: "2026-03-09T10:30:00.000Z" },
57 { role: "user", createdAt: "2026-03-09T10:20:00.000Z" },
58 ],
59 }),
60 makeThread({
61 id: "thread-2",
62 createdAt: "2026-03-09T10:15:00.000Z",
63 updatedAt: "2026-03-09T10:15:00.000Z",
64 }),
65 ],
66 "updated_at",
67 );
68
69 expect(sorted.map((thread) => thread.id)).toEqual(["thread-1", "thread-2"]);
70 });
71});
packages/client-runtime/src/state/threadSort.ts+4-1
...@@ -32,7 +32,10 @@ function getFirstSortableTimestamp(...values: Array<string | null | undefined>):...@@ -32,7 +32,10 @@ function getFirstSortableTimestamp(...values: Array<string | null | undefined>):
3232
33function getLatestUserMessageTimestamp(thread: ThreadSortInput): number {33function getLatestUserMessageTimestamp(thread: ThreadSortInput): number {
34 if (thread.latestUserMessageAt) {34 if (thread.latestUserMessageAt) {
35 return toSortableTimestamp(thread.latestUserMessageAt) ?? Number.NEGATIVE_INFINITY;35 const latestUserMessageTimestamp = toSortableTimestamp(thread.latestUserMessageAt);
36 if (latestUserMessageTimestamp !== null) {
37 return latestUserMessageTimestamp;
38 }
36 }39 }
3740
38 let latestUserMessageTimestamp: number | null = null;41 let latestUserMessageTimestamp: number | null = null;