Crosstalk-Solutions/project-nomad · error · Error

No protomaps builds found

Error message

No protomaps builds found

What it means

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.

Source

Thrown at admin/app/services/map_service.ts:633

        template.layers.push(layer)
      }

      template.sources = Object.assign(template.sources, source)
    }

    template.sprite = sprites
    template.glyphs = glyphs

    return template
  }

  async getGlobalMapInfo(): Promise<ProtomapsBuildInfo> {
    const { default: axios } = await import('axios')
    const response = await axios.get(PROTOMAPS_BUILDS_METADATA_URL, { timeout: 15000 })
    const builds = response.data as Array<{ key: string; size: number }>

    if (!builds || builds.length === 0) {
      throw new Error('No protomaps builds found')
    }

    // Latest build first
    const sorted = builds.sort((a, b) => b.key.localeCompare(a.key))
    const latest = sorted[0]

    const dateStr = latest.key.replace('.pmtiles', '')
    const date = `${dateStr.slice(0, 4)}-${dateStr.slice(4, 6)}-${dateStr.slice(6, 8)}`

    return {
      url: `${PROTOMAPS_BUILD_BASE_URL}/${latest.key}`,
      date,
      size: latest.size,
      key: latest.key,
    }
  }

  async downloadGlobalMap(): Promise<{ filename: string; jobId?: string }> {

View on GitHub (pinned to 0bd1c6f4f9)

Solutions

  1. Check what the metadata URL actually returns: curl -s $PROTOMAPS_BUILDS_METADATA_URL
  2. Handle a wrapped response shape if upstream changed (e.g. response.data.builds)
  3. Retry after a transient upstream issue; pin/upgrade to a version matching the current protomaps API
  4. If self-hosting, point PROTOMAPS_BUILDS_METADATA_URL at a known-good mirror

Example fix

// before
const builds = response.data as Array<{ key: string; size: number }>

// after
const data: any = response.data
const builds = (Array.isArray(data) ? data : data?.builds) as Array<{ key: string; size: number }>
Defensive patterns

Strategy: retry

Validate before calling

const { data } = await axios.get(PROTOMAPS_BUILDS_METADATA_URL, { timeout: 15000 })
const builds = Array.isArray(data) ? data : data?.builds
if (!builds?.length) throw new Error('no builds available; retry later')

Type guard

const isBuildList = (d: unknown): d is Array<{ key: string; size: number }> => Array.isArray(d) && d.every(b => typeof b?.key === 'string')

Try / catch

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 }

Prevention

When it happens

Trigger: Calling getGlobalMapInfo (directly or via downloadGlobalMap) when the upstream protomaps builds listing returns [] or an unexpected shape that evaluates as empty after casting.

Common situations: 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.

Related errors


AI-assisted analysis of Crosstalk-Solutions/project-nomad@0bd1c6f4f9 (2026-08-27). Data as JSON: /api/errors/714d835c78e231f8. Report an issue: GitHub.