{"record":{"id":"f32347a126e24fca","repo":"RocketChat/Rocket.Chat","slug":"marketplace-bad-marketplace-connection","errorCode":null,"errorMessage":"Marketplace_Bad_Marketplace_Connection","messagePattern":"Marketplace_Bad_Marketplace_Connection","errorType":"error_code","errorClass":"MarketplaceConnectionError","httpStatus":null,"severity":"error","filePath":"apps/meteor/ee/server/apps/marketplace/fetchMarketplaceApps.ts","lineNumber":167,"sourceCode":"\n\tlet request;\n\ttry {\n\t\trequest = await Apps.getMarketplaceClient().fetch(`v1/apps`, {\n\t\t\theaders,\n\t\t\tignoreSsrfValidation: false,\n\t\t\tallowList: settings.get<string>('SSRF_Allowlist'),\n\t\t\tparams: {\n\t\t\t\t...(endUserID && { endUserID }),\n\t\t\t},\n\t\t});\n\t} catch (error) {\n\t\t// Offline (air-gapped) licenses reject before any request is made; keep the\n\t\t// typed error so the REST layer can report the real reason instead of a\n\t\t// generic connectivity failure.\n\t\tif (error instanceof CloudOfflineLicenseError) {\n\t\t\tthrow error;\n\t\t}\n\t\tthrow new MarketplaceConnectionError('Marketplace_Bad_Marketplace_Connection');\n\t}\n\n\tif (request.status === 200) {\n\t\tconst response = await request.json();\n\t\tfetchMarketplaceAppsSchema.parse(response);\n\t\treturn response;\n\t}\n\n\tconst response = await request.json();\n\n\tApps.getRocketChatLogger().error({ msg: 'Error fetching marketplace apps', status: request.status, response });\n\n\t// TODO: Refactor cloud to return a proper error code on unsupported version\n\tif (request.status === 426 && 'errorMsg' in response && response.errorMsg === 'unsupported version') {\n\t\tthrow new MarketplaceUnsupportedVersionError();\n\t}\n\n\tif (request.status === 400 && response.code === 200) {","sourceCodeStart":149,"sourceCodeEnd":185,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/f9d3ec372bb580fa8d036f94cf03925a478ef768/apps/meteor/ee/server/apps/marketplace/fetchMarketplaceApps.ts#L149-L185","documentation":"Thrown by fetchMarketplaceApps when the marketplace client fetch to v1/apps throws (network-level failure, not an HTTP error status). The original error is inspected: if it is a CloudOfflineLicenseError (air-gapped/offline license rejecting before any request), it is rethrown as-is; otherwise it is wrapped as MarketplaceConnectionError('Marketplace_Bad_Marketplace_Connection'). HTTP error statuses (4xx/5xx) do NOT hit this path - they are handled downstream.","triggerScenarios":"Calling fetchMarketplaceApps() while the server has no network route to the marketplace host, DNS resolution fails, the TLS handshake fails, or the request is aborted. The CloudOfflineLicenseError branch fires specifically when the workspace runs an offline/air-gapped license that blocks cloud calls entirely.","commonSituations":"Air-gapped deployment; egress proxy/firewall blocking the marketplace host; misconfigured SSRF_Allowlist excluding the marketplace; DNS outage; corporate proxy requiring HTTPS_PROXY env not set; offline license used in an environment expected to be online.","solutions":["Verify outbound network access to the marketplace host from the server.","If behind a proxy, set HTTPS_PROXY / HTTP_PROXY and restart the app.","If using an offline/air-gapped license intentionally, expect CloudOfflineLicenseError instead and handle marketplace features as unavailable.","Ensure SSRF_Allowlist (settings) includes the marketplace host if SSRF validation is in play.","Retry after restoring connectivity."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"async function canReachMarketplaceHost(): Promise<boolean> {\n  try {\n    const res = await Apps.getMarketplaceClient().fetch('v1/categories', { ignoreSsrfValidation: false });\n    return res.status === 200;\n  } catch { return false; }\n}","typeGuard":"import { CloudOfflineLicenseError } from '../../../../lib/errors/CloudOfflineLicenseError';\n\nconst isOfflineLicenseError = (e: unknown): e is CloudOfflineLicenseError =>\n  e instanceof CloudOfflineLicenseError;\n\nconst isConnectionError = (e: unknown): boolean =>\n  e instanceof Error && e.message === 'Marketplace_Bad_Marketplace_Connection';","tryCatchPattern":"import { fetchMarketplaceApps } from './fetchMarketplaceApps';\nimport { CloudOfflineLicenseError } from '../../../../lib/errors/CloudOfflineLicenseError';\n\ntry {\n  return await fetchMarketplaceApps({ endUserID });\n} catch (e) {\n  if (e instanceof CloudOfflineLicenseError) {\n    // air-gapped license - marketplace is intentionally unavailable\n    return [];\n  }\n  if (e instanceof Error && e.message === 'Marketplace_Bad_Marketplace_Connection') {\n    return await retryWithBackoff(() => fetchMarketplaceApps({ endUserID }));\n  }\n  throw e;\n}","preventionTips":["Verify egress to the marketplace host and proxy config before relying on marketplace features.","Add the marketplace host to SSRF_Allowlist if SSRF validation is active.","Distinguish CloudOfflineLicenseError (intentional offline) from real connectivity failures.","Retry transient connection errors with exponential backoff."],"tags":["marketplace","network","connection","ssrf"],"backgroundTag":null,"analyzedSha":"f9d3ec372bb580fa8d036f94cf03925a478ef768","analyzedAt":"2026-08-12T19:07:17.372Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}