Crosstalk-Solutions/project-nomad · warning · Error

Download already in progress for 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.

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.

Appendix: 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)