hcengineering/platform · error · PlatformError
ExpiredLink
ExpiredLink
Error message
ExpiredLink
What it means
checkInvite throws a PlatformError with status ExpiredLink when the invite has a non-zero expiresOn timestamp that is earlier than the current time. The invite link was time-limited and its validity window has passed, so acceptance is refused.
Source
Thrown at server/account/src/utils.ts:1237
ctx.error('Workspace record generation failed. Could not create a workspace record in 1000 attempts.', {
workspaceName
})
throw new PlatformError(
new Status(Severity.ERROR, platform.status.InternalServerError, { region, workspaceName, baseWorkspaceUrl })
)
}
}
}
export async function checkInvite (ctx: MeasureContext, invite: WorkspaceInvite, email: string): Promise<WorkspaceUuid> {
if (invite.remainingUses === 0) {
ctx.warn('Invite limit exceeded', { email, ...invite })
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
}
if (invite.expiresOn > 0 && invite.expiresOn < Date.now()) {
ctx.warn('Invite link expired', { email, ...invite })
throw new PlatformError(new Status(Severity.ERROR, platform.status.ExpiredLink, {}))
}
// TODO: consider not using RegExp with user input as some regexes might
// be slow or even cause catastrophic backtracking
// if (
// invite.emailPattern != null &&
// invite.emailPattern.trim().length > 0 &&
// !new RegExp(invite.emailPattern).test(email)
// ) {
// ctx.error("Invite doesn't allow this email address", { email, ...invite })
// Analytics.handleError(new Error(`Invite link email mask check failed ${invite.id} ${email} ${invite.emailPattern}`))
// throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
// }
if (invite.email != null && invite.email.trim().length > 0 && invite.email !== email) {
ctx.warn("Invite doesn't allow this email address", { email, ...invite })
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
}View on GitHub (pinned to 63e28dc964)
Solutions
- Request a fresh invite link from the workspace admin.
- Have an admin extend or clear the invite's expiresOn field.
- If expiry is intentional, create a new invite with a longer validity period.
- If the date is only slightly in the past, verify server clocks are synchronized (NTP).
Example fix
// admin side: renew an expired invite
// before
invite.expiresOn < Date.now()
// after
await account.updateInvite(ctx, invite._id, { expiresOn: Date.now() + 7 * 24 * 3600 * 1000 }) Defensive patterns
Strategy: validation
Validate before calling
// check before attempting acceptance
if (invite.expiresOn > 0 && invite.expiresOn < Date.now()) {
throw new Error('This invite link has expired; request a new one')
} Type guard
function isInviteActive(invite: { expiresOn: number }): boolean {
return invite.expiresOn === 0 || invite.expiresOn >= Date.now()
} Try / catch
try {
await account.acceptInvite(ctx, token, email)
} catch (err) {
if (extractStatus(err, platform) === platform.status.ExpiredLink) {
// show 'link expired' UI and offer to request a fresh invite
} else throw err
} Prevention
- Show expiry dates on invite links in the admin UI.
- Send reminder emails before invites expire.
- Use generous expiry windows unless security demands otherwise.
- Validate expiresOn client-side before submitting the acceptance form.
When it happens
Trigger: Accepting a workspace invite via checkInvite where invite.expiresOn > 0 && invite.expiresOn < Date.now(), i.e. any time after the invite's configured expiration date.
Common situations: Old invite links saved in emails or chat history and reused weeks later; invites created with short expiry windows in security-conscious workspaces; clock skew is rare but possible.
Related errors
AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29).
Data as JSON: /api/errors/fac796999312c509.
Report an issue: GitHub.