{"record":{"id":"c2629adeb8d828a0","repo":"unslothai/unsloth","slug":"analysis-analysis-id-did-not-complete-before-the","errorCode":null,"errorMessage":"analysis {analysis_id} did not complete before the deadline","messagePattern":"analysis (.+?) did not complete before the deadline","errorType":"exception","errorClass":"TimeoutError","httpStatus":null,"severity":"error","filePath":"scripts/virustotal_scan.py","lineNumber":542,"sourceCode":"                # attempt, not a dead end: VirusTotal may well be analysing the file\n                # already. Raising straight out would report the asset unavailable\n                # after we had paid the disclosure cost of sending it, so spend the\n                # remaining attempt on a fresh signed URL instead.\n                last_error = RuntimeError(\"VirusTotal upload did not return an analysis id\")\n                if attempt < attempts:\n                    continue\n                raise last_error\n            return analysis_id\n\n        raise RuntimeError(f\"VirusTotal upload failed after {attempts} attempt(s): {last_error}\")\n\n    def wait_for_analysis(self, analysis_id: str, deadline: float) -> object:\n        \"\"\"Poll until the analysis completes or the caller's deadline passes.\"\"\"\n        while True:\n            # Checked inside request() too, but raising the analysis-specific message\n            # here keeps the summary row readable.\n            if self._clock() >= deadline:\n                raise TimeoutError(f\"analysis {analysis_id} did not complete before the deadline\")\n            _, payload = self.request(\n                \"GET\", f\"{API_ROOT}/analyses/{analysis_id}\", deadline = deadline\n            )\n            attributes = _attributes(payload)\n            if attributes.get(\"status\") == \"completed\":\n                return payload\n            if self._request_interval <= 0:\n                # With throttling disabled (premium key) the loop would otherwise spin.\n                self._sleep(1.0)\n\n\ndef _build_multipart(path: Path) -> tuple[bytes, str]:\n    \"\"\"Encode `path` as a single-part multipart/form-data body under the field `file`.\"\"\"\n    boundary = f\"----UnslothDesktopScan{uuid.uuid4().hex}\"\n    head = (\n        f\"--{boundary}\\r\\n\"\n        f'Content-Disposition: form-data; name=\"file\"; filename=\"{path.name}\"\\r\\n'\n        \"Content-Type: application/octet-stream\\r\\n\\r\\n\"","sourceCodeStart":524,"sourceCodeEnd":560,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/scripts/virustotal_scan.py#L524-L560","documentation":"TimeoutError raised inside wait_for_analysis when the monotonic clock reaches the caller-supplied deadline before the VirusTotal analysis for the given analysis id reports status \"completed\". It is a deadline violation on the polling loop, not an HTTP failure: every poll succeeded but the remote analysis was still queued/running.","triggerScenarios":"Polling GET {API_ROOT}/analyses/{analysis_id} until the deadline while VirusTotal still returns status 'queued' or 'running'; a very large uploaded file whose analysis exceeds the deadline; VirusTotal backend backlogs; a deadline computed too tight relative to the file size.","commonSituations":"Scanning multi-hundred-MB model or dataset artifacts where VirusTotal analysis routinely takes many minutes; deadline derived from a global run timeout that leaves only seconds for analysis; periods of VirusTotal slowness; request interval misconfigured so polls are too sparse to observe completion in time.","solutions":["Increase the deadline passed to wait_for_analysis, sized to the uploaded file's size (large files take minutes, not seconds).","Retain the analysis id: the upload itself succeeded, so you can poll the same id again later instead of re-uploading and paying the disclosure cost twice.","Check the analysis status via GET /analyses/{id} manually to see whether it is queued vs failed.","If this happens consistently for small files, check VirusTotal service status and your request interval/throttling settings."],"exampleFix":"# before\ndeadline = time.monotonic() + 60\npayload = vt.wait_for_analysis(analysis_id, deadline)\n\n# after\ndeadline = time.monotonic() + 15 * 60  # large uploads take minutes\npayload = vt.wait_for_analysis(analysis_id, deadline)","handlingStrategy":"retry","validationCode":"deadline = max(deadline, time.monotonic() + size_based_budget(path))  # e.g. 60s per 100MB, floor 5min","typeGuard":null,"tryCatchPattern":"try:\n    payload = vt.wait_for_analysis(analysis_id, deadline)\nexcept TimeoutError:\n    # upload succeeded; persist analysis_id and poll again later instead of re-uploading\n    schedule_retry(analysis_id)","preventionTips":["Scale the polling deadline with uploaded file size.","Persist the analysis id so a timeout never forces a second upload of sensitive assets.","Distinguish TimeoutError from RuntimeError in handlers: one means 'wait longer', the other means 'upload failed'."],"tags":["virustotal","timeout","polling"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}