{"record":{"id":"3acd708726523741","repo":"Stirling-Tools/Stirling-PDF","slug":"missing-stored-file-id-in-response","errorCode":null,"errorMessage":"Missing stored file ID in response.","messagePattern":"Missing stored file ID in response\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"frontend/editor/src/core/services/serverStorageUpload.ts","lineNumber":66,"sourceCode":"  );\n  const formData = new FormData();\n  formData.append(\"file\", finalFile, finalFile.name);\n  formData.append(\"historyBundle\", bundleFile, bundleFile.name);\n  formData.append(\"auditLog\", auditLog, auditLog.name);\n\n  if (existingRemoteId) {\n    const response = await apiClient.put(\n      `/api/v1/storage/files/${existingRemoteId}`,\n      formData,\n    );\n    const updatedAt = resolveUpdatedAt(response.data?.updatedAt);\n    return { remoteId: existingRemoteId, updatedAt, chain };\n  }\n\n  const response = await apiClient.post(\"/api/v1/storage/files\", formData);\n  const remoteId = response.data?.id as number | undefined;\n  if (!remoteId) {\n    throw new Error(\"Missing stored file ID in response.\");\n  }\n\n  const updatedAt = resolveUpdatedAt(response.data?.updatedAt);\n  return { remoteId, updatedAt, chain };\n}\n\nexport async function uploadHistoryChains(\n  originalFileIds: FileId[],\n  existingRemoteId?: number,\n): Promise<{ remoteId: number; updatedAt: number; chain: StirlingFileStub[] }> {\n  const uniqueRoots = Array.from(new Set(originalFileIds));\n  const chainMap = new Map<FileId, StirlingFileStub[]>();\n  const combinedChain: StirlingFileStub[] = [];\n  const seenIds = new Set<FileId>();\n  const leafStubs: StirlingFileStub[] = [];\n\n  for (const rootId of uniqueRoots) {\n    const chain = await fileStorage.getHistoryChainStubs(rootId);","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/Stirling-Tools/Stirling-PDF/blob/9ef20dcab80b85041912f045e17a6aea1d08f969/frontend/editor/src/core/services/serverStorageUpload.ts#L48-L84","documentation":"Thrown by uploadHistoryChain when POST /api/v1/storage/files returned without throwing (axios 2xx) but response.data.id is falsy. The backend storage endpoint did not return a numeric stored-file identifier.","triggerScenarios":"The backend responded 2xx but the body lacks an id field, or the field was renamed/moved; an interceptor reshaped the payload; or a 2xx auth/HTML body was captured.","commonSituations":"Frontend/backend version mismatch where the endpoint contract changed; Spring Security returned a login page captured as 200; a proxy/gateway rewrote the envelope; response wrapped under data.file.id.","solutions":["Confirm the backend POST /api/v1/storage/files returns { id: number } at response.data.id.","Inspect the actual response body in the browser network tab.","Align frontend and backend versions so the contract matches.","Ensure the request was not silently redirected to an auth/login route."],"exampleFix":"// before\nconst remoteId = response.data?.id as number | undefined;\nif (!remoteId) throw new Error('Missing stored file ID in response.');\n\n// after\nconst remoteId = response.data?.id ?? response.data?.file?.id;\nif (typeof remoteId !== 'number' || !Number.isFinite(remoteId)) {\n  console.error('Unexpected storage response:', response.data);\n  throw new Error('Storage endpoint did not return a file id.');\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  const res = await apiClient.post('/api/v1/storage/files', formData);\n  const id = res.data?.id;\n  if (typeof id !== 'number') {\n    throw new Error(`Storage response missing numeric id: ${JSON.stringify(res.data).slice(0, 200)}`);\n  }\n  return { remoteId: id, updatedAt: resolveUpdatedAt(res.data?.updatedAt) };\n} catch (e) {\n  // distinguish network error from contract error for the user\n  throw e instanceof Error ? e : new Error('Upload failed');\n}","preventionTips":["Pin frontend and backend to matching versions; the /api/v1/storage/files id contract is load-bearing.","Log the raw response body when id is missing so contract drift is diagnosable.","Watch for auth redirects that return HTML with a 2xx status."],"tags":["api-contract","backend","storage","network"],"backgroundTag":null,"analyzedSha":"9ef20dcab80b85041912f045e17a6aea1d08f969","analyzedAt":"2026-08-13T22:11:39.827Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}