RocketChat/Rocket.Chat · error
App metadata download failed
Error message
App metadata download failed
What it means
Thrown during marketplace app installation when the second parallel request — fetching app metadata from v1/apps/:appId?appVersion=:version on the cloud marketplace — rejects. Like the package download (162) it is a transport-level wrapper: the original network error is preserved as { cause }. Only the download-vs-metadata branch differs; metadata failing means the marketplace never answered the info request even though install proceeded this far.
Solutions
- Read err.cause in the server log to get the real network error (ENOTFOUND, ETIMEDOUT, certificate, etc.).
- Stabilize egress: fix proxy/DNS/TLS config on the server host so parallel requests to marketplace.rocket.chat both succeed.
- Retry the install; transient drops of one of the two parallel requests are common and a retry usually passes.
- If persistent, test each endpoint directly from the host: curl 'https://marketplace.rocket.chat/v1/apps/<appId>?appVersion=<version>' to confirm which URL is failing.
Example fix
// before
try {
await installFromMarketplace(appId, version);
} catch (e) {
console.log(e.message); // "App metadata download failed" — real reason hidden
}
// after: log the cause chain to identify the network problem
try {
await installFromMarketplace(appId, version);
} catch (e) {
console.error(e.message, { cause: e.cause }); // e.g. TypeError: fetch failed: ETIMEDOUT
} Defensive patterns
Strategy: retry
Try / catch
try {
await installFromMarketplace(appId, version);
} catch (e) {
if (e instanceof Error && e.message === 'App metadata download failed') {
const cause = (e.cause as Error)?.message ?? 'unknown';
// ETIMEDOUT/ENOTFOUND -> fix egress then retry; do not blind-retry more than twice
}
throw e;
} Prevention
- Keep egress stable for parallel marketplace requests (proxy connection limits > 2).
- Retry installs once or twice with backoff; persistent metadata failures indicate config, not luck.
- Capture the cause chain in monitoring so the real network error is never lost.
When it happens
Trigger: POST to the marketplace-install route where the metadata fetch inside Promise.all rejects: network partition, 5xx that makes fetch throw through the client, timeout to marketplace.rocket.chat. The download branch may succeed; the whole install still aborts and the error is logged as 'Error installing app from marketplace:'.
Common situations: Flaky egress link that passes one request but drops the parallel one; cloud marketplace partial outage affecting only the v1 info endpoint; proxy rate-limiting concurrent connections; MTU/keepalive issues killing the second socket of Promise.all.
Related errors
- App package download failed
- Invalid response from the Marketplace
- Invalid url. It doesn't exist or is not "application/zip".
- Marketplace_Failed_To_Fetch_Apps
- Marketplace_Failed_To_Fetch_Categories
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/a214cab059ffb48e.
Report an issue: GitHub.
Appendix: source
Thrown at apps/meteor/ee/server/apps/communication/rest.ts:322
.fetch(`v2/apps/${this.bodyParams.appId}/download/${this.bodyParams.version}?token=${downloadToken}`, {
headers,
// SECURITY: user needs specific privileges to send this. Bypassing the SSRF check is okay for now.
ignoreSsrfValidation: true,
})
.catch((cause) => {
throw new Error('App package download failed', { cause });
}),
Apps.getMarketplaceClient()
.fetch(`v1/apps/${this.bodyParams.appId}?appVersion=${this.bodyParams.version}`, {
headers: {
Authorization: `Bearer ${marketplaceToken}`,
...headers,
},
// SECURITY: user needs specific privileges to send this. Bypassing the SSRF check is okay for now.
ignoreSsrfValidation: true,
})
.catch((cause) => {
throw new Error('App metadata download failed', { cause });
}),
]);
if (downloadResponse.headers.get('content-type') !== 'application/zip') {
throw new Error('Invalid url. It doesn\'t exist or is not "application/zip".');
}
buff = Buffer.from(await downloadResponse.arrayBuffer());
marketplaceInfo = await marketplaceResponse.json();
// Note: marketplace responds with an array of the marketplace info on the app, but it is expected
// to always have one element since we are fetching a specific app version.
if (!Array.isArray(marketplaceInfo) || marketplaceInfo?.length !== 1) {
orchestrator.getRocketChatLogger().error({ msg: 'Error getting app information from marketplace', marketplaceInfo });
throw new Error('Invalid response from the Marketplace');
}
permissionsGranted = this.bodyParams.permissionsGranted;View on GitHub (pinned to b2c16d5842)