moderator: invalidateUserSessions throws while the signal beside it deliberately swallows — decide the asymmetry
apps/moderator/src/lib/server/sessions.ts has two side effects in invalidateUserSessions, and they have opposite failure semantics — one deliberately, one apparently by omission.
export async function invalidateUserSessions(userId: number): Promise<void> {
await get().invalidateUserSessions(userId); // <- unwrapped: throws to the caller
await sendSessionSignal(userId); // <- try/catch, deliberately swallowed
}sendSessionSignal carries an explicit comment explaining its swallow: "Best-effort on purpose, matching the main app: a signals outage must not fail the mute that has already been written." The registry call above it has no such comment and no handler, so a backend failure there rejects invalidateUserSessions and propagates into every caller — mute, unmute, ban, force-logout.
This may well be correct. A revocation that did not happen should arguably not report success, and that is a different situation from a signal that merely failed to reach an open tab. But right now the asymmetry is incidental rather than decided: one line documents its choice and the other does not, so the next person to touch this cannot tell whether the difference is intentional.
Worth deciding explicitly, because the two options are meaningfully different for a moderator:
- keep it throwing — the moderator sees the action fail and can retry, at the cost of a 500 on a path where the DB write may already have happened
- degrade like its neighbour — the mute lands, the revocation is logged as failed, and the user keeps a live session until it expires
Note the file directly above already reasons about a related trap: "Revoking a token makes the NEXT request fail; it does not reach a client that is already connected." And image-moderation-effects.ts in the same app was changed for precisely this class of problem — its header records moderators getting "actioning items still causes 500 error, but does action" (2026-08-12) and now runs post-write side effects so they cannot fail the action that already happened. invalidateUserSessions is a post-write side effect of the same shape and did not get that treatment.
Not a bug report
I have not observed this failing in production, and I am not claiming it currently does. It surfaced while auditing this app's Redis wiring for a separate, unrelated configuration problem. The ask is a decision and a comment, not necessarily a code change.
Closing condition
Either (a) a comment is added beside get().invalidateUserSessions(userId) stating why it throws while sendSessionSignal swallows, and this issue closes on that PR merging; or (b) it is changed to degrade like its neighbour, and this issue closes on that PR merging.
Checked by: @ZacxDev, by reading the resulting PR — the decision is what closes this, not the code shape it lands on.
Source: civitai/civitai