Stirling-Tools/Stirling-PDF · error · Error

Missing stored file ID in response.

Error message

Missing stored file ID in response.

What it means

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.

Source

Thrown at frontend/editor/src/core/services/serverStorageUpload.ts:66

  );
  const formData = new FormData();
  formData.append("file", finalFile, finalFile.name);
  formData.append("historyBundle", bundleFile, bundleFile.name);
  formData.append("auditLog", auditLog, auditLog.name);

  if (existingRemoteId) {
    const response = await apiClient.put(
      `/api/v1/storage/files/${existingRemoteId}`,
      formData,
    );
    const updatedAt = resolveUpdatedAt(response.data?.updatedAt);
    return { remoteId: existingRemoteId, updatedAt, chain };
  }

  const response = await apiClient.post("/api/v1/storage/files", formData);
  const remoteId = response.data?.id as number | undefined;
  if (!remoteId) {
    throw new Error("Missing stored file ID in response.");
  }

  const updatedAt = resolveUpdatedAt(response.data?.updatedAt);
  return { remoteId, updatedAt, chain };
}

export async function uploadHistoryChains(
  originalFileIds: FileId[],
  existingRemoteId?: number,
): Promise<{ remoteId: number; updatedAt: number; chain: StirlingFileStub[] }> {
  const uniqueRoots = Array.from(new Set(originalFileIds));
  const chainMap = new Map<FileId, StirlingFileStub[]>();
  const combinedChain: StirlingFileStub[] = [];
  const seenIds = new Set<FileId>();
  const leafStubs: StirlingFileStub[] = [];

  for (const rootId of uniqueRoots) {
    const chain = await fileStorage.getHistoryChainStubs(rootId);

View on GitHub (pinned to 9ef20dcab8)

Solutions

  1. Confirm the backend POST /api/v1/storage/files returns { id: number } at response.data.id.
  2. Inspect the actual response body in the browser network tab.
  3. Align frontend and backend versions so the contract matches.
  4. Ensure the request was not silently redirected to an auth/login route.

Example fix

// before
const remoteId = response.data?.id as number | undefined;
if (!remoteId) throw new Error('Missing stored file ID in response.');

// after
const remoteId = response.data?.id ?? response.data?.file?.id;
if (typeof remoteId !== 'number' || !Number.isFinite(remoteId)) {
  console.error('Unexpected storage response:', response.data);
  throw new Error('Storage endpoint did not return a file id.');
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
  const res = await apiClient.post('/api/v1/storage/files', formData);
  const id = res.data?.id;
  if (typeof id !== 'number') {
    throw new Error(`Storage response missing numeric id: ${JSON.stringify(res.data).slice(0, 200)}`);
  }
  return { remoteId: id, updatedAt: resolveUpdatedAt(res.data?.updatedAt) };
} catch (e) {
  // distinguish network error from contract error for the user
  throw e instanceof Error ? e : new Error('Upload failed');
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of Stirling-Tools/Stirling-PDF@9ef20dcab8 (2026-08-13). Data as JSON: /api/errors/3acd708726523741. Report an issue: GitHub.