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
- Check the job status for that URL (RunDownloadJob.getByUrl) and wait for it to finish instead of retrying
- Debounce/disable the trigger in the UI until the job completes
- 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
- Disable submit buttons until the current job resolves
- Poll job status by URL before triggering a new download
- Clean up stale job records from dead workers so the guard cannot deadlock
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.