Skip to content

Read a channel cursor with a malformed time as the first page, not a 500 - #640

Merged
davidmckayv merged 2 commits into
CopilotKit:mainfrom
Ayush7614:fix/channel-people-strict-cursor
Oct 2, 2026
Merged

davidmckayv merged 2 commits into
CopilotKit:mainfrom
Ayush7614:fix/channel-people-strict-cursor

Conversation

@Ayush7614

@Ayush7614 Ayush7614 commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

A channel cursor's recency was only checked to be a string before the page query cast it with ::timestamptz, so a corrupted cursor answered 500. It now has to be the UTC timestamp the encoder writes (millisecond or microsecond), or the cursor reads as the first page, like every other malformed cursor.

Narrowed by a maintainer from the original version, which turned malformed channel and people cursors into a 400.

@davidmckayv

Copy link
Copy Markdown
Contributor

Needs changes before merge:

  • Date.parse accepts strings Postgres rejects, so the 500 this fixes still happens. "1", "2026-02-30" and "+099999-01-01T00:00:00Z" all pass Number.isNaN(Date.parse(x)) and then fail at ::timestamptz. Check that the value round-trips instead, new Date(x).toISOString() === x, in both the channel recency and the people lastSignedInAt checks, and add tests for those three strings.
  • The description and four code comments say a malformed cursor made a client loop forever. It does not: main returns page one with a valid nextCursor, so a client pages normally and stops. The real defect is the 500 on a well-formed cursor with a non-date recency, plus consistency with the audit endpoint's 400. Correct the description and the comments.
  • Add a CHANGELOG Unreleased entry: GET /api/channels and GET /api/admin/people now return 400 for a malformed cursor, or one issued before the pinned field existed, instead of page one. An empty or absent cursor still means page one.

decodeChannelCursor only checked that recency was a string, and the page
query casts it with ::timestamptz, so a hand-edited cursor answered 500.
A recency that is not the UTC timestamp the encoder writes now reads as
the first page, like every other malformed cursor.
@davidmckayv
davidmckayv force-pushed the fix/channel-people-strict-cursor branch from 702d570 to e7f68cd Compare October 2, 2026 18:38
@davidmckayv davidmckayv changed the title fix(paging): refuse malformed channel and people cursors with 400 Read a channel cursor with a malformed time as the first page, not a 500 Oct 2, 2026
@davidmckayv

Copy link
Copy Markdown
Contributor

I narrowed this to the channel bug and force-pushed it to your branch. decodeChannelCursor now checks that recency is the timestamp the encoder writes, and falls back to page one otherwise. That is the 500 this PR found.

I dropped the 400s. server/src/channels/routes.ts documents treating a malformed cursor as the first page on purpose ("the honest answer to a stale link"), and a 400 would break a cursor minted before pinned existed. The people cursor already rejects a bad lastSignedInAt with Date.parse (server/src/people/store.ts:125), so it needed no change. The commit is under your name.

@davidmckayv
davidmckayv merged commit 4a20750 into CopilotKit:main Oct 2, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants