{"record":{"id":"714d835c78e231f8","repo":"Crosstalk-Solutions/project-nomad","slug":"no-protomaps-builds-found","errorCode":null,"errorMessage":"No protomaps builds found","messagePattern":"No protomaps builds found","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"admin/app/services/map_service.ts","lineNumber":633,"sourceCode":"        template.layers.push(layer)\n      }\n\n      template.sources = Object.assign(template.sources, source)\n    }\n\n    template.sprite = sprites\n    template.glyphs = glyphs\n\n    return template\n  }\n\n  async getGlobalMapInfo(): Promise<ProtomapsBuildInfo> {\n    const { default: axios } = await import('axios')\n    const response = await axios.get(PROTOMAPS_BUILDS_METADATA_URL, { timeout: 15000 })\n    const builds = response.data as Array<{ key: string; size: number }>\n\n    if (!builds || builds.length === 0) {\n      throw new Error('No protomaps builds found')\n    }\n\n    // Latest build first\n    const sorted = builds.sort((a, b) => b.key.localeCompare(a.key))\n    const latest = sorted[0]\n\n    const dateStr = latest.key.replace('.pmtiles', '')\n    const date = `${dateStr.slice(0, 4)}-${dateStr.slice(4, 6)}-${dateStr.slice(6, 8)}`\n\n    return {\n      url: `${PROTOMAPS_BUILD_BASE_URL}/${latest.key}`,\n      date,\n      size: latest.size,\n      key: latest.key,\n    }\n  }\n\n  async downloadGlobalMap(): Promise<{ filename: string; jobId?: string }> {","sourceCodeStart":615,"sourceCodeEnd":651,"githubUrl":"https://github.com/Crosstalk-Solutions/project-nomad/blob/0bd1c6f4f9888d577fe232de06ac144bb8337131/admin/app/services/map_service.ts#L615-L651","documentation":"getGlobalMapInfo fetches the protomaps builds metadata JSON from PROTOMAPS_BUILDS_METADATA_URL and expects a non-empty array of {key,size} entries. If the endpoint returns an empty array (or a falsy body after a 200), this error is thrown because there is no build to download.","triggerScenarios":"Calling getGlobalMapInfo (directly or via downloadGlobalMap) when the upstream protomaps builds listing returns [] or an unexpected shape that evaluates as empty after casting.","commonSituations":"Upstream API format change (fields renamed, response wrapped in an object so the array cast yields nothing); temporary CDN hiccup returning an empty list; a proxy or mirror serving stale/empty metadata; axios returning a string body that is truthy but empty when parsed.","solutions":["Check what the metadata URL actually returns: curl -s $PROTOMAPS_BUILDS_METADATA_URL","Handle a wrapped response shape if upstream changed (e.g. response.data.builds)","Retry after a transient upstream issue; pin/upgrade to a version matching the current protomaps API","If self-hosting, point PROTOMAPS_BUILDS_METADATA_URL at a known-good mirror"],"exampleFix":"// before\nconst builds = response.data as Array<{ key: string; size: number }>\n\n// after\nconst data: any = response.data\nconst builds = (Array.isArray(data) ? data : data?.builds) as Array<{ key: string; size: number }>","handlingStrategy":"retry","validationCode":"const { data } = await axios.get(PROTOMAPS_BUILDS_METADATA_URL, { timeout: 15000 })\nconst builds = Array.isArray(data) ? data : data?.builds\nif (!builds?.length) throw new Error('no builds available; retry later')","typeGuard":"const isBuildList = (d: unknown): d is Array<{ key: string; size: number }> => Array.isArray(d) && d.every(b => typeof b?.key === 'string')","tryCatchPattern":"try { return await mapService.getGlobalMapInfo() } catch (e) { if (e instanceof Error && e.message === 'No protomaps builds found') { await delay(10_000); return mapService.getGlobalMapInfo() } throw e }","preventionTips":["Add exponential-backoff retries around upstream metadata calls","Pin to a known build key instead of always resolving 'latest'","Monitor the upstream protomaps builds endpoint for schema changes"],"tags":["network","upstream-api","protomaps"],"backgroundTag":"upstream-api-empty-response","analyzedSha":"0bd1c6f4f9888d577fe232de06ac144bb8337131","analyzedAt":"2026-08-27T05:34:15.424Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}