Crosstalk-Solutions/project-nomad · warning · Error
Download already in progress for URL ${url}
Error message
Download already in progress for URL ${url} What it means
Concurrency guard in MapService.downloadRemote: before starting a new download job it queries RunDownloadJob.getActiveByUrl(url) and throws if an active job for the same URL already exists, preventing duplicate concurrent downloads of the same PMTiles file.
Source
Thrown at admin/app/services/map_service.ts:266
logger.info(
`[MapService] Kept prior ${parsed.resource_id} file (reason: ${decision.reason})`
)
}
} catch (error) {
logger.error(`[MapService] Failed to create InstalledResource for ${filename}:`, error)
}
}
}
async downloadRemote(url: string): Promise<{ filename: string; jobId?: string }> {
const parsed = new URL(url)
if (!parsed.pathname.endsWith('.pmtiles')) {
throw new Error(`Invalid PMTiles file URL: ${url}. URL must end with .pmtiles`)
}
const existing = await RunDownloadJob.getActiveByUrl(url)
if (existing) {
throw new Error(`Download already in progress for URL ${url}`)
}
const filename = url.split('/').pop()
if (!filename) {
throw new Error('Could not determine filename from URL')
}
const filepath = join(process.cwd(), this.mapStoragePath, 'pmtiles', filename)
// First, ensure base assets are present - regions depend 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
- Wait for or monitor the existing job (its id is tracked in RunDownloadJob) instead of starting a new one
- Disable/retry-guard the submit button while a job is pending
- If no job is actually running, mark the stale RunDownloadJob record as failed/completed so the guard clears
- Idempotently attach to the existing job at the API layer and return its jobId
Example fix
// before
await mapService.downloadRemote(url) // second click: throws
// after
const active = await runDownloadJobService.getByUrl(url)
if (active) return { jobId: active.id } // attach to existing
return mapService.downloadRemote(url) Defensive patterns
Strategy: fallback
Validate before calling
const active = await runDownloadJobRepo.findOne({ where: { url, status: In(['active','running']) } })
if (active) return { jobId: active.id, alreadyRunning: true } // attach, don't error
return mapService.downloadRemote(url) Type guard
const isDuplicateDownloadError = (e: unknown): boolean =>
(e as Error).message.startsWith('Download already in progress') Try / catch
try {
await mapService.downloadRemote(url)
} catch (e) {
if ((e as Error).message.startsWith('Download already in progress')) {
const job = await runDownloadJobService.getActiveByUrl(url)
return { jobId: job?.id, status: 'in-progress' } // fallback: join existing job
}
throw e
} Prevention
- Debounce/disable the submit button while a job is pending
- Return the existing job id from the API instead of erroring (idempotent design)
- Reconcile stale 'active' job rows on worker startup via a heartbeat/timeout
When it happens
Trigger: Calling downloadRemote twice for the same URL while the first job is still running — double-clicked submit, retried request, or two admins triggering the import simultaneously.
Common situations: Frontend retry/backoff resubmitting before completion, webhook/poll-driven triggers racing, stale 'active' job records stuck in the DB after a crashed worker blocking future downloads.
Related errors
- Invalid PMTiles file URL: ${url}. URL must end with .pmtiles
- Download already in progress for URL ${info.url}
- Preflight returned no data
- An unknown error occurred during the preflight check.
- res.message
AI-assisted analysis of Crosstalk-Solutions/project-nomad@0bd1c6f4f9 (2026-08-27).
Data as JSON: /api/errors/c83f5b5066c8c3d4.
Report an issue: GitHub.