Repository navigation
fix(slack): Normalize email casing in linkExistingSlackMembers - #58
Merged
tylermenezes merged 1 commit intoSep 18, 2026
Conversation
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.
Detail bug report: View on Detail
Bug
linkExistingSlackMembers(the hourly Slack-sync cron fallback) matched apply-time DB emails against Slack'susers.listprofile.emailusing JavaScript's case-sensitiveinoperator on an un-normalized lookup, and itspreviousParticipantsPrisma-incopy was similarly case-sensitive. Any student/mentor whose apply-time email casing differs from the casing Slack holds (e.g. DBFirst.Last@Company.comvs Slackfirst.last@company.com) was silently never matched, leavingslackIdnull indefinitely — so downstream consumers that filter onslackIdtreated the user as absent from the workspace. This was inconsistent with every other cross-system email join in the codebase (postmark.ts, Attio sync), which normalize casing.Fix
normalizeEmail(email) = email.toLowerCase().trim()and applied it to both sides of every comparison insrc/slack/linkExistingSlackMembers.ts: thesearchStudents/searchMentorslookup keys, theusers.listmember filter and row-id lookup, and thepreviousParticipantsin-memory match/lookup/delete.previousParticipantsPrismafindManynow passesemail: { in: [...], mode: 'insensitive' }so prior-event rows with differing casing are found on a default case-sensitive Postgres collation.deps?: { prisma?, slack? }injection seam tolinkExistingSlackMembers(defaults toContainer.get(PrismaClient)+getSlackClientForEvent(event)), so the cron path is unit-testable without a live DB / Slack workspace. The sole runtime caller (slackSync.ts) is unchanged and backward-compatible.mode: 'insensitive'is supported by the installed Prisma client'sStringFilterand was verified against a real Postgres 14 instance (defaulten_US.utf8collation).Testing
tests/testLinkExistingSlackMembers.ts(5node:testcases):normalizeEmaillowercase+trim, student case-mismatch linking (the reported bug), mentor case-mismatch linking, whitespace-divergent linking (guards the.trim()half), and thepreviousParticipantscase-insensitive copy — which also asserts the Prisma query carriesmode: 'insensitive'and that the prior copy removes the row from the search set beforeusers.listruns.tests/testSlackReporting.ts,npx tsc --skipLibCheck --noEmit, andnpm run buildall pass. (Nolintis available — the repo's pinned@typescript-eslintparser fails under the installedtypescript@5.2.2, independently of this change.)prisma db push(the migration chain hits a pre-existing ordering quirk on fresh DBs), seeded an event/project plus case-mismatched, exact-case, whitespace-divergent, and already-linked rows, then drove the real fixed function against the live DB with a mockusers.listreturning lowercased casings. All affected rows acquired the expectedslackId; the already-linked row was untouched. A data-layer join demonstration confirmed the case-sensitive join (the bug) matches 1/4 of the seeded null rows while the case-insensitive join (the fix) matches 4/4.users.listcapture could not be run: no Slack token is available in this environment (env | grep -iE SLACKis empty; no.env), so the run would be rejected by Slack. It was substituted with a mockusers.listpayload modeling the lowercasedprofile.emailcasings Slack returns; the Postgres half was fully exercised.Automatic Fixes PRs can be configured here.