{"record":{"id":"7f8c24a013979199","repo":"actualbudget/actual","slug":"uuid-format-no-longer-matches-expected-format","errorCode":null,"errorMessage":"UUID format no longer matches expected format","messagePattern":"UUID format no longer matches expected format","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/sync-server/src/app-sync.ts","lineNumber":73,"sourceCode":"  express.raw({\n    type: 'application/encrypted-file',\n    limit: `${config.get('upload.syncEncryptedFileSizeLimitMB')}mb`,\n  }),\n);\napp.use(express.json({ limit: `${config.get('upload.fileSizeLimitMB')}mb` }));\n\nexport { app as handlers };\n\nconst OK_RESPONSE = { status: 'ok' };\n\nfunction boolToInt(deleted: boolean) {\n  return deleted ? 1 : 0;\n}\n\nfunction generateGroupId(): GroupId {\n  const id = uuidv4();\n  if (!isValidGroupId(id)) {\n    throw new TypeError('UUID format no longer matches expected format');\n  }\n  return id;\n}\n\nfunction extractSingleHeader(\n  req: Request,\n  res: Response,\n  key: string,\n): string | null {\n  const value = req.headers[key];\n  if (!value) {\n    return null;\n  }\n  if (typeof value !== 'string') {\n    res.status(400).send('Duplicate headers encountered for key ' + key);\n    return null;\n  }\n  return value;","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/sync-server/src/app-sync.ts#L55-L91","documentation":"generateGroupId creates a sync GroupId via uuidv4() and then re-validates it with isValidGroupId (a guard against the UUID format changing across library upgrades). If the generated UUID no longer matches the expected format, it throws a TypeError 'UUID format no longer matches expected format'. This is a defensive invariant check, essentially unreachable in normal operation.","triggerScenarios":"Only when uuidv4() returns a value that fails isValidGroupId — practically meaning the uuid library was swapped/upgraded to produce a different format (e.g. non-lowercase or braced UUIDs) or mocked to return non-UUID values.","commonSituations":"Dependency upgrade or replacement of the uuid package; test mocks stubbing uuidv4 with values like 'mock-id'; custom crypto.randomUUID polyfills returning a different casing/format.","solutions":["Check the installed uuid package version/implementation and restore standard v4 output (lowercase 8-4-4-4-12).","If uuidv4 is mocked in tests, make the mock return a valid v4 UUID string.","Inspect isValidGroupId and update it only if the ID format intentionally changed across the codebase.","Reinstall dependencies (yarn install) to rule out a corrupted/pinned wrong uuid version."],"exampleFix":"// before\nvi.mock('uuid', () => ({ v4: () => 'test-id' }));\n\n// after\nvi.mock('uuid', () => ({ v4: () => '0b9e6b1e-7a4c-4f3e-9d2a-1c8b5f6e7a90' }));","handlingStrategy":"type-guard","validationCode":"function isValidGroupId(id: string): boolean {\n  return /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(id);\n}\nconst id = uuidv4();\nif (!isValidGroupId(id)) throw new TypeError('uuid source produced invalid v4 id');","typeGuard":"function isUuidV4(value: string): value is `${string}-${string}-${string}-${string}-${string}` {\n  return /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(value);\n}","tryCatchPattern":null,"preventionTips":["Never mock uuidv4 with non-UUID strings in tests.","Keep the uuid package on a standard v4 implementation across upgrades.","Treat this throw as an invariant violation: fix the uuid source, not the guard."],"tags":["sync","uuid","internal-invariant","typescript"],"backgroundTag":"uuid-format-mismatch","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}