{"record":{"id":"3af4cf13b5cf4415","repo":"RocketChat/Rocket.Chat","slug":"app-package-download-failed","errorCode":null,"errorMessage":"App package download failed","messagePattern":"App package download failed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/ee/server/apps/communication/rest.ts","lineNumber":310,"sourceCode":"\t\t\t\t\t\t} catch (err: any) {\n\t\t\t\t\t\t\torchestrator.getRocketChatLogger().error({ msg: 'Error fetching App from URL:', err });\n\t\t\t\t\t\t\treturn API.v1.internalError();\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if ('appId' in this.bodyParams && this.bodyParams.appId && this.bodyParams.marketplace && this.bodyParams.version) {\n\t\t\t\t\t\tconst headers = getDefaultHeaders();\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst downloadToken = await getWorkspaceAccessToken(true, 'marketplace:download', false);\n\t\t\t\t\t\t\tconst marketplaceToken = await getWorkspaceAccessToken();\n\n\t\t\t\t\t\t\tconst [downloadResponse, marketplaceResponse] = await Promise.all([\n\t\t\t\t\t\t\t\tApps.getMarketplaceClient()\n\t\t\t\t\t\t\t\t\t.fetch(`v2/apps/${this.bodyParams.appId}/download/${this.bodyParams.version}?token=${downloadToken}`, {\n\t\t\t\t\t\t\t\t\t\theaders,\n\t\t\t\t\t\t\t\t\t\t// SECURITY: user needs specific privileges to send this. Bypassing the SSRF check is okay for now.\n\t\t\t\t\t\t\t\t\t\tignoreSsrfValidation: true,\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t.catch((cause) => {\n\t\t\t\t\t\t\t\t\t\tthrow new Error('App package download failed', { cause });\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\tApps.getMarketplaceClient()\n\t\t\t\t\t\t\t\t\t.fetch(`v1/apps/${this.bodyParams.appId}?appVersion=${this.bodyParams.version}`, {\n\t\t\t\t\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t\t\t\t\tAuthorization: `Bearer ${marketplaceToken}`,\n\t\t\t\t\t\t\t\t\t\t\t...headers,\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t// SECURITY: user needs specific privileges to send this. Bypassing the SSRF check is okay for now.\n\t\t\t\t\t\t\t\t\t\tignoreSsrfValidation: true,\n\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t.catch((cause) => {\n\t\t\t\t\t\t\t\t\t\tthrow new Error('App metadata download failed', { cause });\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t]);\n\n\t\t\t\t\t\t\tif (downloadResponse.headers.get('content-type') !== 'application/zip') {\n\t\t\t\t\t\t\t\tthrow new Error('Invalid url. It doesn\\'t exist or is not \"application/zip\".');\n\t\t\t\t\t\t\t}","sourceCodeStart":292,"sourceCodeEnd":328,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/f9d3ec372bb580fa8d036f94cf03925a478ef768/apps/meteor/ee/server/apps/communication/rest.ts#L292-L328","documentation":"Thrown during marketplace app install (POST /apps with appId+version+marketplace) when the call to fetch the app zip from v2/apps/{appId}/download/{version} rejects. The original rejection is preserved as the cause (new Error('App package download failed', { cause })). The route runs both the package download and the metadata fetch in parallel via Promise.all, so a failure on either short-circuits.","triggerScenarios":"POST /api/v1/apps with body { appId, version, marketplace: true } while the workspace cannot reach the marketplace, the download token is invalid/expired, the version does not exist on the marketplace, or the marketplace host returns a non-2xx/throwing response for the download URL.","commonSituations":"Air-gapped or proxy-restricted server without outbound access to the marketplace; expired/missing workspace registration token (getWorkspaceAccessToken for the download scope failed silently); user requested a version that was unpublished; transient marketplace outage or DNS failure.","solutions":["Check outbound connectivity from the server to the marketplace host (verify Site_Url and proxy settings).","Confirm the workspace is registered (Administration > Connectivity Services) so getWorkspaceAccessToken returns a valid download token.","Retry the install; transient marketplace failures often resolve on retry.","Verify the requested appId/version exist on the marketplace; an unpublished or yanked version will 404.","Inspect the preserved cause in logs (the logged err includes the original fetch error) for the real HTTP status."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"async function canReachMarketplace(): Promise<boolean> {\n  try {\n    const res = await fetch('https://marketplace.rocket.chat/health', { method: 'GET' });\n    return res.ok;\n  } catch { return false; }\n}\n\nif (!await canReachMarketplace()) {\n  throw new Error('Marketplace unreachable; cannot download app package');\n}","typeGuard":"const isInstallPayload = (body: unknown): body is { appId: string; version: string; marketplace: true } =>\n  typeof body === 'object' &&\n  typeof (body as any)?.appId === 'string' &&\n  typeof (body as any)?.version === 'string' &&\n  (body as any)?.marketplace === true;","tryCatchPattern":"try {\n  await installMarketplaceApp(appId, version);\n} catch (e) {\n  if (e instanceof Error && e.message === 'App package download failed') {\n    // e.cause holds the original fetch error - inspect it\n    console.error('Underlying cause:', (e as Error & { cause?: Error }).cause);\n    retryWithBackoff();\n  }\n}","preventionTips":["Ensure workspace registration is active before install (valid download token).","Verify outbound connectivity and proxy config in advance.","Retry install once after a short backoff for transient marketplace errors.","Log e.cause, not just e.message, when this error surfaces."],"tags":["apps-engine","marketplace","network","install"],"backgroundTag":null,"analyzedSha":"f9d3ec372bb580fa8d036f94cf03925a478ef768","analyzedAt":"2026-08-12T19:07:17.372Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}