{"record":{"id":"a384a0847c1c17ff","repo":"Stirling-Tools/Stirling-PDF","slug":"password-removal-did-not-produce-a-file","errorCode":null,"errorMessage":"Password removal did not produce a file.","messagePattern":"Password removal did not produce a file\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"frontend/editor/src/core/contexts/FileContext.tsx","lineNumber":420,"sourceCode":"        );\n      }\n\n      const params: RemovePasswordParameters = { password };\n      const formData = buildRemovePasswordFormData(params, file);\n\n      const response = await apiClient.post(\n        \"/api/v1/security/remove-password\",\n        formData,\n        {\n          responseType: \"blob\",\n          suppressErrorToast: true, // Handle errors in modal UI instead of toast\n        },\n      );\n      const responseFiles = await processResponse(response.data, [file]);\n\n      const unlockedFile = responseFiles[0];\n      if (!unlockedFile) {\n        throw new Error(\n          t(\n            \"encryptedPdfUnlock.emptyResponse\",\n            \"Password removal did not produce a file.\",\n          ),\n        );\n      }\n\n      const processedMetadata =\n        await generateProcessedFileMetadata(unlockedFile);\n      const thumbnail = processedMetadata?.thumbnailUrl;\n\n      const operation: ToolOperation = {\n        toolId: \"removePassword\",\n        timestamp: Date.now(),\n      };\n\n      const childStub = createChildStub(\n        parentStub,","sourceCodeStart":402,"sourceCodeEnd":438,"githubUrl":"https://github.com/Stirling-Tools/Stirling-PDF/blob/9ef20dcab80b85041912f045e17a6aea1d08f969/frontend/editor/src/core/contexts/FileContext.tsx#L402-L438","documentation":"Thrown in runAutomaticPasswordRemoval() when processResponse(response.data, [file]) returns an empty array — the server's /api/v1/security/remove-password endpoint returned a response, but no extractable File was produced from it. This typically means the server returned an error body (not a PDF blob) that processResponse could not interpret as a file. The message is i18n-localized via encryptedPdfUnlock.emptyResponse.","triggerScenarios":"The password was incorrect and the server returned an error JSON/HTML instead of a PDF blob, but with a 200 status. Or the server returned an empty response body. The suppressErrorToast flag means no toast was shown, so this throw is the only signal to the modal UI.","commonSituations":"Wrong password submitted (server returns error in body with 200). Server bug returning empty body. Response format change where processResponse no longer recognizes the content type.","solutions":["Check response headers Content-Type before calling processResponse — if it's not application/pdf, parse as error JSON.","Validate response.data.size > 0 (for blob responses) before processing.","Inspect the raw response in the catch block to distinguish wrong-password from server errors.","Add server-side validation to return proper HTTP error codes (401/403) for wrong passwords instead of 200 with error body."],"exampleFix":"// before\nconst responseFiles = await processResponse(response.data, [file]);\nconst unlockedFile = responseFiles[0];\nif (!unlockedFile) {\n  throw new Error(t(\"encryptedPdfUnlock.emptyResponse\", \"Password removal did not produce a file.\"));\n}\n\n// after\nconst contentType = response.headers?.[\"content-type\"] ?? \"\";\nif (!contentType.includes(\"application/pdf\")) {\n  const errorText = await response.data.text();\n  throw new Error(`Server did not return a PDF: ${errorText.slice(0, 200)}`);\n}\nconst responseFiles = await processResponse(response.data, [file]);\nconst unlockedFile = responseFiles[0];\nif (!unlockedFile) {\n  throw new Error(t(\"encryptedPdfUnlock.emptyResponse\", \"Password removal did not produce a file.\"));\n}","handlingStrategy":"try-catch","validationCode":"// Check Content-Type and blob size before processing\nconst contentType = response.headers?.['content-type'] ?? '';\nif (!contentType.includes('application/pdf')) {\n  // Server returned an error body, not a PDF\n  const errorText = await (response.data as Blob).text();\n  throw new Error(`Password removal failed: ${errorText.slice(0, 200)}`);\n}\nif (response.data.size === 0) {\n  throw new Error('Server returned an empty response.');\n}","typeGuard":"function isPdfBlob(response: AxiosResponse): boolean {\n  const ct = response.headers?.['content-type'] ?? '';\n  return ct.includes('application/pdf') && response.data instanceof Blob && response.data.size > 0;\n}","tryCatchPattern":"try {\n  const response = await apiClient.post('/api/v1/security/remove-password', formData, {\n    responseType: 'blob',\n    suppressErrorToast: true,\n  });\n  if (!isPdfBlob(response)) {\n    const errorText = await response.data.text();\n    setModalError(`Server error: ${errorText.slice(0, 200)}`);\n    return;\n  }\n  // process the valid PDF\n} catch (error) {\n  setModalError('Password removal failed. Please check the password and try again.');\n}","preventionTips":["Check response Content-Type before assuming the blob is a PDF.","Validate blob size > 0 before calling processResponse.","Ensure the server returns proper HTTP error codes (401/403) for wrong passwords.","Test with wrong passwords to verify the error path surfaces correctly in the modal."],"tags":["password-removal","api","file-processing","response-parsing","i18n"],"backgroundTag":null,"analyzedSha":"9ef20dcab80b85041912f045e17a6aea1d08f969","analyzedAt":"2026-08-13T22:11:39.827Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}