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

  1. Read err.cause in the server log to get the real network error (ENOTFOUND, ETIMEDOUT, certificate, etc.).
  2. Stabilize egress: fix proxy/DNS/TLS config on the server host so parallel requests to marketplace.rocket.chat both succeed.
  3. Retry the install; transient drops of one of the two parallel requests are common and a retry usually passes.
  4. 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

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


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)