{"record":{"id":"5ac9a00f1b3d7bd8","repo":"jamiepine/voicebox","slug":"rocm-backend-download-already-in-progress","errorCode":null,"errorMessage":"ROCm backend download already in progress","messagePattern":"ROCm backend download already in progress","errorType":"http","errorClass":"HTTPException","httpStatus":409,"severity":"warning","filePath":"backend/routes/rocm.py","lineNumber":32,"sourceCode":"\n\n@router.get(\"/backend/rocm-status\")\nasync def get_rocm_status():\n    \"\"\"Get ROCm backend download/availability status.\"\"\"\n    from ..services import rocm\n\n    return rocm.get_rocm_status()\n\n\n@router.post(\"/backend/download-rocm\")\nasync def download_rocm_backend():\n    \"\"\"Download the ROCm backend binary.\"\"\"\n    from ..services import rocm\n\n    progress_manager = get_progress_manager()\n    existing = progress_manager.get_progress(rocm.PROGRESS_KEY)\n    if existing and existing.get(\"status\") in {\"downloading\", \"extracting\"}:\n        raise HTTPException(status_code=409, detail=\"ROCm backend download already in progress\")\n\n    async def _download():\n        try:\n            await rocm.download_rocm_binary()\n        except Exception as e:\n            logger.error(\"ROCm download failed: %s\", e)\n\n    create_background_task(_download())\n    return {\"message\": \"ROCm backend download started\", \"progress_key\": rocm.PROGRESS_KEY}\n\n\n@router.delete(\"/backend/rocm\")\nasync def delete_rocm_backend():\n    \"\"\"Delete the downloaded ROCm backend binary.\"\"\"\n    from ..services import rocm\n\n    if rocm.is_rocm_active():\n        raise HTTPException(","sourceCodeStart":14,"sourceCodeEnd":50,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/routes/rocm.py#L14-L50","documentation":"Returned by POST /backend/download-rocm when an existing progress entry for rocm.PROGRESS_KEY already has status 'downloading' or 'extracting' (HTTP 409). The handler reads progress_manager.get_progress and treats any in-flight state as a conflict, preventing concurrent downloads. It is a deliberate guard, not a transient failure.","triggerScenarios":"Calling download-rocm while a previous download/extract is still running. A prior download task crashed without updating progress status away from 'downloading'/'extracting', leaving a stale in-flight entry.","commonSituations":"User double-clicks the download button. Frontend retries on a slow network without checking progress. Background task died (process restart) but progress status was never reset to failed/done.","solutions":["Poll GET /backend/rocm-progress (SSE) to observe the existing download's status before re-issuing POST.","If the status is genuinely stuck ('downloading' indefinitely after a crash), reset the progress entry via the service or restart the backend, then retry.","Disable the download button in the UI while status is downloading/extracting.","Treat 409 as 'already started, subscribe to progress' rather than an error to retry."],"exampleFix":"// before\nawait api.post('/backend/download-rocm');\n\n// after\nconst status = await getRocmStatus();\nif (status === 'downloading' || status === 'extracting') {\n  return subscribeToProgress();\n}\nawait api.post('/backend/download-rocm');","handlingStrategy":"retry","validationCode":"async function rocmDownloadActive() {\n  const s = await (await fetch('/api/backend/rocm-status')).json();\n  return s.status === 'downloading' || s.status === 'extracting';\n}","typeGuard":"function isRocmInProgress(s) { return s && (s.status === 'downloading' || s.status === 'extracting'); }","tryCatchPattern":"try { await api.post('/backend/download-rocm'); }\ncatch (e) {\n  if (e.response?.status === 409) { await subscribeToProgress(); return; }\n  throw e;\n}","preventionTips":["Poll /backend/rocm-progress before starting a download.","Disable the download button while status is downloading/extracting.","If progress is stuck after a crash, reset the entry or restart the backend before retrying."],"tags":["fastapi","http","conflict","rocm","download","rest"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}