company_id Is Always Null on Public/Share Links
This article may contain affiliate links. Its content is not affected by advertising.
In short
An invite record's company_id is only set via an invite token, so on public, share, and QR links it is always null — letting a company re-book a slot it already holds.
Conclusion
Don’t source the company ID for a “does this company already hold everything” duplicate check from the invite record. Applications that don’t go through an invite token — public pages, share links, QR codes — never create an invite record at all, so the company ID is always null on those paths. If the duplicate-check side skips its entire check whenever the ID is null, slots the company already holds never get excluded from the candidate pool, get booked again, and the company is billed twice.
Symptom
The system has a “bundle discount” feature: when an applicant checks “include related slots too” on the application form, the server re-derives the list of candidate slots and books, as a bundle, only the ones the company doesn’t already hold.
On the public page that doesn’t use an invite token, and on share links or QR codes issued by a referral partner, slots the company already held got pulled back into the candidate pool, booked again, and billed again.
Cause
The function that resolves bundle-application candidates only runs its “exclude slots already held” logic when it receives a company ID:
if (companyId) {
const { data: held } = await admin
.from("placements")
.select("loop_id")
.eq("company_id", companyId)
.in("availability", ACTIVE);
const heldSet = new Set(
((held ?? []) as { loop_id: string | null }[]).map((r) => r.loop_id).filter(Boolean)
);
memberIds = memberIds.filter((id) => !heldSet.has(id));
if (memberIds.length === 0) return null;
}
If companyId isn’t passed, the whole if block never runs. That’s not “skip the exclusion” — it’s “the exclusion logic never existed for this call.”
The caller sourced that company ID straight from the invite record:
const offer = await getQualifierBundleOffer(
createSupabaseAdminClient(),
loopId,
invite?.company?.id ?? null
);
invite only holds a value when an invite token was successfully verified. On public, share, and QR-code applications, invite itself is always null. So invite?.company?.id is always undefined, and ?? null pins it to null. The exclusion check never worked on any application path that didn’t go through an invite.
The fix
Instead of the invite record’s ID, the code now uses the company ID that the booking step immediately before it (book) had already resolved:
// ⚠ Use the ID that booking #1 already resolved via name-matching. The
// invite's company ID is always null on share/QR/public paths, which
// silently disables the "exclude already-held slots" check.
const offer = await getQualifierBundleOffer(admin, loopId, result.companyId);
result is the return value of the handler that booked this application in the first place, and it carries a company ID resolved from the company name and other signals regardless of whether an invite was involved. Using it means the exclusion check now works correctly on public, share, and QR-code applications too.
Whether the discount was actually granted was also decoupled from whether it could be offered. Being offered a discount is not the same as receiving one. Whether it was actually granted is now confirmed only by the actual write to the contracts table, and when that write doesn’t happen, the response says explicitly that no discount was applied.
Preventing a repeat
This shape of bug tends to show up in code written as if (companyId) { ... } — “add a safety check only when a value is present.” When the value is missing, that pattern doesn’t fall back to a safe default; it falls back to the safety check simply not existing, and tests tend to skate right past the null case too. For a value that acts as the key to a duplicate-prevention check, like a company ID, the fix here was to first guarantee the value reaches every calling path without going missing, and only then write the guard on the receiving side — not the other way around.
For a similar shape of bug — adding one seemingly unrelated path silently disarms an existing safeguard — see adding one more subdomain to a full-zone DNS submission silently deleted an unrelated A record. That one was about the scope of what gets sent; this one is about the condition used to decide. The shape of the failure is the same.
Frequently asked questions
Q1Why did the company ID come back null?
The ID passed into the duplicate check was read from the invite record (invite.company.id). Public pages, share links, and QR codes never create an invite record, so invite?.company?.id was always undefined and ?? null pinned it to null.
Q2What breaks when the ID is null?
The duplicate-check function only excludes already-held slots when it receives a company ID. Null skips that branch entirely — every held slot stays in the candidate pool, gets rebooked under the bundle discount, and the company is billed twice.
Q3How was it fixed?
The code now uses the companyId the booking step just before it (book) had already resolved from the company name. That value exists regardless of whether an invite was involved, so the check now works on public, share, and QR applications too.
Q4Does the same bug hit invite-based applications?
No. An invite-based application has a company ID from the start, so this null-out can't happen there. It's confined to applications that skip the invite token: public, share, and QR links.
Environment verified
- Next.js (kimiteras-portal) / Supabase admin client
- Fixed under 2026-07-24 code-review comment B2
What this article is based on
- TypeScript file lines 147-158commit 4314479
- TypeScript file lines 172-205commit 4314479
- TypeScript file lines 149-155commit 8d1878b
Every claim in this article comes from the records above. The repositories we operate are private so we cannot link to them, but which file, which lines, and at which commit we read them is recorded for every article. Nothing here is written from guesswork.