Keep a person's last sign-in when their sessions go away - #466
Open
zopeVaibhav wants to merge 1 commit into
Open
Keep a person's last sign-in when their sessions go away#466zopeVaibhav wants to merge 1 commit into
zopeVaibhav wants to merge 1 commit into
Conversation
zopeVaibhav
requested review from
MikeRyanDev,
davidmckayv,
guidovizoso and
tylerslaton
as code owners
September 9, 2026 17:25
9 tasks
zopeVaibhav
force-pushed
the
fix/record-last-sign-in
branch
from
September 10, 2026 16:59
11fd767 to
18fa020
Compare
zopeVaibhav
force-pushed
the
fix/record-last-sign-in
branch
from
September 11, 2026 17:21
18fa020 to
200cc04
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #465
What this changes
A person's last sign-in is now stored on the person, instead of being recomputed from whichever
sessions happen to still exist. Sessions expire, get cleaned up, and are deleted outright when
somebody is removed, so the old value disappeared exactly when an administrator most wanted it.
users.last_signed_in_at(server/drizzle/0029_last_signed_in_at.sql), backfilled from thesessions that still exist at upgrade time (
server/drizzle/0030_backfill_last_signed_in_at.sql).stampSignInwrites it in the session-createafterhook (server/src/auth/index.ts:46-66,called at
:309). It isgreatest(coalesce(existing, new), new), so the stamp can never movebackwards, and the whole write is wrapped — a failure logs
sign-in-stamp-write-failedand thesign-in still succeeds.
sessions(
server/src/people/store.ts:199), so the keyset comparison moves from aHAVINGon the aggregateto a
WHEREon the column (:170-181).(
app/src/routes/_authed/admin/people.tsx:57-58); it previously showed only "Access removed" andthe provider, which was the case where the date was most missing.
What this cannot fix: anybody whose sessions were already gone before the migration runs stays
blank. The backfill can only read evidence that still exists.
Where it runs
sign-in.
greatestmakes the write order-independent: two processes stamping the same personconverge on the later time rather than the last writer's.
is computed from the existing column inside the same statement, not read-then-written.
Boundary and audit
sign-in that has already been decided.
— it is not a refusal, and it must not be able to stop somebody signing in. It is logged.
session the library just created.
No permission changes. The value was already on this administrator-only screen; it is now correct
there.
Changelog
Unreleased: "The People screen keeps a person's last sign-in when their sessions goaway".
Proof
The bug, on
origin/main1c7bd92, through main's own API. A person with one session row, readthrough
PeopleStore.find, then removed throughPeopleStore.revoke:Written against the surface
mainalready has, so it fails on the behaviour rather than on amissing export.
Live, before and after, against the same database. On this branch a person showing
"last signed in 31/08/2026" keeps that line after every one of their session rows is deleted. On
main, with exactly the same rows, the same person reads "never signed in" and drops to the bottomof the list beside somebody who has genuinely never arrived — the API answers
"lastSignedInAt": nullfor them.
server/tests/people-last-sign-in.integration.test.tsadds six passing tests: the removal, thesign-out, the ordering across a removal and a restore, the stamp landing on one person only, an
out-of-order stamp, and a stamp that cannot be written not refusing the sign-in.
Gates. Typecheck clean across app, server and worker; lint 605 files, format 601, no diff. Both
migrations applied to a live database, and
drizzle-kit generateafterwards reportsNo schema changes, nothing to migrate, so the snapshot and the schema agree.Migration numbering. This work predates
0028_audit_initiatorlanding onmain. The column andthe backfill were regenerated as
0029and0030on top of it rather than renumbered by hand, andthe generated SQL is identical to what it replaced.
mainstill tops out at0028, so the numberingis current as of this rebase.
Suite, rebased onto
1c7bd92and run in one session against a same-session control, with the twoagent-handoff-*.integrationfiles excluded because they fail most of their tests on baremainandmake any comparison meaningless:
origin/main@1c7bd92The failing test names are byte-identical on both sides, as is the
@langchain/core/messagesresolution error, so none of them belong to this change. The delta is exactly this PR's one new file
and its six tests. The paging file's test count is unchanged and covers the keyset move.