Crosstalk-Solutions/project-nomad · warning · Error

Download already in progress for URL ${info.url}

Error message

Download already in progress for URL ${info.url}

What it means

downloadGlobalMap resolves the latest world basemap info and checks RunDownloadJob.getByUrl for an existing job on that URL. If one exists (in any non-terminal state), it throws this guard error to prevent duplicate concurrent downloads of the same global map.

Source

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

    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 }> {
    const info = await this.getGlobalMapInfo()

    const existing = await RunDownloadJob.getByUrl(info.url)
    if (existing) {
      throw new Error(`Download already in progress for URL ${info.url}`)
    }

    const basePath = resolve(join(this.baseDirPath, 'pmtiles'))
    const filepath = resolve(join(basePath, info.key))

    // Prevent path traversal — resolved path must stay within the storage directory
    if (!filepath.startsWith(basePath + sep)) {
      throw new Error('Invalid filename')
    }

    // First, ensure base assets are present - the global map depends on them
    const baseAssetsExist = await this.ensureBaseAssets()
    if (!baseAssetsExist) {
      throw new Error(
        'Base map assets are missing and could not be downloaded. Please check your connection and try again.'
      )
    }

View on GitHub (pinned to 0bd1c6f4f9)

Solutions

  1. Check the job status for that URL (RunDownloadJob.getByUrl) and wait for it to finish instead of retrying
  2. Debounce/disable the trigger in the UI until the job completes
  3. If a previous job is stuck (worker died), cancel/complete the stale job record so a new download can start

Example fix

// before
await mapService.downloadGlobalMap() // throws if already queued

// after
const existing = await RunDownloadJob.getByUrl(latestUrl)
if (existing) { await pollJob(existing.id); return }
await mapService.downloadGlobalMap()
Defensive patterns

Strategy: validation

Validate before calling

const info = await mapService.getGlobalMapInfo()
const existing = await RunDownloadJob.getByUrl(info.url)
if (existing) { console.log('already queued:', existing.id); return existing }
await mapService.downloadGlobalMap()

Try / catch

try { await mapService.downloadGlobalMap() } catch (e) { if (e instanceof Error && e.message.startsWith('Download already in progress')) { /* treat as success/no-op: job already queued */ return } throw e }

Prevention

When it happens

Trigger: Invoking downloadGlobalMap twice before the first job finishes — e.g. double-clicking a UI button, two admins triggering it, or a retry while the original job is still queued/running.

Common situations: Frontend missing debounce/disable-after-submit; cron or startup hook racing with a manual trigger; a stuck job row in run_download_jobs (crashed worker) that never completes, permanently blocking new downloads.

Related errors


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