{"record":{"id":"f4a29542973a067a","repo":"TryGhost/Ghost","slug":"your-username-is-not-a-valid-mastodon-username","errorCode":null,"errorMessage":"Your Username is not a valid Mastodon Username","messagePattern":"Your Username is not a valid Mastodon Username","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"apps/admin/src/settings/app/utils/social-urls/mastodon.ts","lineNumber":49,"sourceCode":"        // If there's a second @, validate that part too\n        if (rest.includes('@')) {\n            const [, userInstance] = rest.split('@');\n            if (!validator.isFQDN(userInstance)) {\n                throw new Error(errMessage);\n            }\n        }\n\n        return `https://${normalizedUrl}`;\n    }\n\n    throw new Error(errMessage);\n}\n\n// Converts a Mastodon handle to URL\nexport const mastodonHandleToUrl = (handle: string) => {\n    const errMessage = 'Your Username is not a valid Mastodon Username';\n    if (!handle) {\n        throw new Error(errMessage);\n    }\n\n    // Check if it's in @username@instance format\n    if (handle.match(/^@[^@]+@[^/]+$/)) {\n        const [username, instance] = handle.split('@').slice(1);\n        if (!validator.isFQDN(instance)) {\n            throw new Error(errMessage);\n        }\n        return `https://${instance}/@${username}`;\n    }\n\n    // Check if it's in instance/@username format\n    if (handle.match(/^[^/]+\\.[^/]+\\/@[^/]+(@[^/]+)?$/)) {\n        const [instance, rest] = handle.split('/@');\n        if (!validator.isFQDN(instance)) {\n            throw new Error(errMessage);\n        }\n","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/TryGhost/Ghost/blob/47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe/apps/admin/src/settings/app/utils/social-urls/mastodon.ts#L31-L67","documentation":"Thrown by mastodonHandleToUrl when converting a stored handle to a canonical URL: the handle is empty, or doesn't match either Mastodon handle pattern (@username@instance or instance/@username), or the instance portion fails validator.isFQDN. This is the handle→URL direction, used when rendering a stored Mastodon profile link.","triggerScenarios":"mastodonHandleToUrl(handle) where handle is '' (empty → immediate throw), or handle matches ^@[^@]+@[^/]+$ / ^[^/]+\\.[^/]+\\/@[^/]+(@[^/]+)?$ but the instance isn't an FQDN, or handle matches neither pattern at all (e.g. 'someuser', 'user@mastodon').","commonSituations":"Stored handle is empty due to data corruption or incomplete migration; handle was persisted before stricter validation and doesn't conform; UI renders a profile link from a handle that was never validated; concurrent deletion left an empty string.","solutions":["Guard against empty handles at the call site before invoking mastodonHandleToUrl.","Validate/normalise handles at write time using validateMastodonUrl so only well-formed handles are ever stored.","If rendering from untrusted stored data, wrap in try-catch and fall back to omitting the link rather than crashing the UI.","Audit stored Mastodon handles for ones that no longer pass validation after a regex/validator version change."],"exampleFix":"// before — throws (and can crash a render) on empty/corrupt stored handle\nconst url = mastodonHandleToUrl(profile.mastodon);\n\n// after — guard empty, try-catch the rest\nconst handle = profile.mastodon?.trim();\nif (!handle) {\n    return null;\n}\ntry {\n    return mastodonHandleToUrl(handle);\n} catch {\n    return null; // skip rendering the link for invalid stored handles\n}","handlingStrategy":"try-catch","validationCode":"// Guard empty handles before calling the converter\nfunction safeMastodonHandleToUrl(handle: string | undefined | null): string | null {\n    const h = handle?.trim();\n    if (!h) return null;\n    // quick shape check before invoking the throwing converter\n    if (!/^@[^@]+@[^/]+$/.test(h) && !/^[^/]+\\.[^/]+\\/@[^/]+(@[^/]+)?$/.test(h)) return null;\n    return null; // caller should still try-catch for FQDN failures\n}","typeGuard":"null","tryCatchPattern":"const handle = profile.mastodon?.trim();\nif (!handle) return null;\ntry {\n    return mastodonHandleToUrl(handle);\n} catch {\n    return null; // skip rendering the link for invalid stored handles\n}","preventionTips":["Guard handleToUrl against empty strings at the call site.","Validate/normalise handles at write time with validateMastodonUrl so only well-formed handles are stored.","When rendering from stored data, wrap in try-catch and fall back to omitting the link.","Audit stored handles after validator/regex version changes."],"tags":["mastodon","social-urls","validation","federated"],"backgroundTag":null,"analyzedSha":"47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe","analyzedAt":"2026-08-13T01:25:26.651Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}