{"record":{"id":"1ec4abec7e78a2f3","repo":"can1357/oh-my-pi","slug":"pushbullet-upload-fields-were-missing-from-the-des","errorCode":null,"errorMessage":"Pushbullet upload fields were missing from the destination response","messagePattern":"Pushbullet upload fields were missing from the destination response","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/uploaders-cloud-drives.ts","lineNumber":415,"sourceCode":"\tconst authHeaders = { Authorization: `Basic ${btoa(`${apiKey}:`)}` };\n\n\treturn {\n\t\tdestination: \"pushbullet\",\n\t\tasync upload(request: BlobUploadRequest) {\n\t\t\tconst filename = fileNameFor(request);\n\t\t\tconst requestForm = new FormData();\n\t\t\trequestForm.set(\"file_name\", filename);\n\t\t\tconst uploadRequestResponse = await expectOk(\n\t\t\t\tawait fetchImpl(`${PUSHBULLET_API}/upload-request`, {\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\theaders: authHeaders,\n\t\t\t\t\tbody: requestForm,\n\t\t\t\t}),\n\t\t\t\t\"pushbullet\",\n\t\t\t);\n\t\t\tconst uploadRequest = (await uploadRequestResponse.json()) as PushbulletUploadRequest;\n\t\t\tconst data = uploadRequest.data;\n\t\t\tif (!data) throw new Error(\"Pushbullet upload fields were missing from the destination response\");\n\t\t\tconst fileUrl = requiredText(uploadRequest.file_url, \"Pushbullet file URL\");\n\t\t\tconst fileType = requiredText(uploadRequest.file_type, \"Pushbullet file type\");\n\t\t\tconst uploadUrl = requiredText(uploadRequest.upload_url, \"Pushbullet presigned upload URL\");\n\t\t\tconst uploadFields = {\n\t\t\t\tawsaccesskeyid: requiredText(data.awsaccesskeyid, \"Pushbullet AWS access key id\"),\n\t\t\t\tacl: requiredText(data.acl, \"Pushbullet upload ACL\"),\n\t\t\t\tkey: requiredText(data.key, \"Pushbullet upload key\"),\n\t\t\t\tsignature: requiredText(data.signature, \"Pushbullet upload signature\"),\n\t\t\t\tpolicy: requiredText(data.policy, \"Pushbullet upload policy\"),\n\t\t\t\t\"content-type\": requiredText(data[\"content-type\"], \"Pushbullet upload content type\"),\n\t\t\t};\n\t\t\tawait expectOk(\n\t\t\t\tawait fetchImpl(uploadUrl, {\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\tbody: multipartFile(request, \"file\", uploadFields),\n\t\t\t\t}),\n\t\t\t\t\"pushbullet\",\n\t\t\t);","sourceCodeStart":397,"sourceCodeEnd":433,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/uploaders-cloud-drives.ts#L397-L433","documentation":"This error is thrown by the Pushbullet blob uploader after it calls POST /v2/upload-request. Pushbullet's upload-request response must contain `data` — an object of S3 presigned-POST fields (awsaccesskeyid, acl, key, signature, policy, content-type) needed to upload the file bytes. When the response body has no `data` object, the uploader cannot perform the S3 upload and throws instead of producing a partially-broken publication.","triggerScenarios":"POST to https://api.pushbullet.com/v2/upload-request returned 2xx but the parsed JSON body lacked a truthy `data` field — e.g. Pushbullet changed the response schema, a proxy/API gateway stripped or reshaped the body, or the endpoint returned a JSON body that is not the expected {file_url, upload_url, data:{...}} shape.","commonSituations":"Pushbullet API schema changes; corporate proxies or mock/stub servers returning a simplified 200 body; wrong or revoked API key that yields an unexpected 2xx body; tests hitting a mocked Pushbullet endpoint that only stubs part of the response.","solutions":["Verify the API key is valid by calling GET /v2/user and confirm the account can create upload requests","Log the raw upload-request response body to inspect what Pushbullet actually returned","Check for a proxy, API gateway, or mock intercepting api.pushbullet.com and altering the body","Pin/monitor the Pushbullet API for breaking changes and update the uploader's response parsing","Retry the upload — transient gateway issues can produce empty bodies"],"exampleFix":"// before (stubbed/mock endpoint returns partial body)\nconst uploadRequest = await response.json();\n// after (caller validates before treating it as valid)\nconst uploadRequest = await response.json();\nif (!uploadRequest?.data?.policy || !uploadRequest?.upload_url) {\n  throw new Error(`Pushbullet upload-request response incomplete: ${JSON.stringify(uploadRequest).slice(0, 200)}`);\n}","handlingStrategy":"validation","validationCode":"function isValidPushbulletUploadRequest(body) {\n  return !!body && typeof body === 'object' &&\n    typeof body.upload_url === 'string' &&\n    typeof body.file_url === 'string' &&\n    !!body.data && typeof body.data.policy === 'string' &&\n    typeof body.data.signature === 'string';\n}\nconst uploadRequest = await res.json();\nif (!isValidPushbulletUploadRequest(uploadRequest)) {\n  throw new Error('Pushbullet upload-request response incomplete');\n}","typeGuard":"function isPushbulletUploadRequest(v) {\n  return typeof v === 'object' && v !== null && 'data' in v &&\n    typeof v.upload_url === 'string';\n}","tryCatchPattern":"try {\n  const uploadRequest = await uploadRequestResponse.json();\n  if (!isPushbulletUploadRequest(uploadRequest)) {\n    throw new Error(`Pushbullet upload-request incomplete: ${JSON.stringify(uploadRequest).slice(0, 200)}`);\n  }\n} catch (err) {\n  logger.error('Pushbullet upload-request failed', { cause: err });\n  throw err;\n}","preventionTips":["Validate the upload-request response shape before using its fields","Use a real, unproxied Pushbullet API key in production","Log unexpected response bodies to detect API schema changes early","Test against the live Pushbullet API, not partial mocks"],"tags":["pushbullet","api-response","network","schema-change"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}