{"record":{"id":"a55e5195943b4b1b","repo":"MuntashirAkon/AppManager","slug":"scan-error","errorCode":null,"errorMessage":"Scan error: ","messagePattern":"Scan error: ","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"app/src/main/java/io/github/muntashirakon/AppManager/scanner/vt/VirusTotal.java","lineNumber":170,"sourceCode":"        int waitDuration = 60_000;\n        while (queued || responseReport.shouldRetry()) {\n            if (waitFirst) {\n                // Effectively makes it a do-while loop\n                waitFirst = false;\n            } else {\n                responseReport = fetchFileReport(checksum);\n                queued = responseReport.response != null && !responseReport.response.hasReport();\n            }\n            // Wait for result: First wait for 1 minute, then for 30 seconds\n            // We won't do it less than 30 seconds since the API has a limit of 4 request/minute\n            SystemClock.sleep(waitDuration);\n            // TODO: 23/5/22 Wait duration should be according to the fileSize\n            waitDuration = 30_000;\n        }\n        if (responseReport.response != null) {\n            response.onReportReceived(responseReport.response);\n        } else {\n            throw new IOException(\"Scan error: \" + responseReport.error);\n        }\n    }\n\n    @WorkerThread\n    @NonNull\n    public ResponseV3<String> uploadFile(@NonNull String filename, @NonNull InputStream is)\n            throws IOException {\n        return uploadFile(filename, is, null);\n    }\n\n    @WorkerThread\n    @NonNull\n    public ResponseV3<String> uploadFile(@NonNull String filename, @NonNull InputStream is,\n                                         @Nullable String password) throws IOException {\n        URL url = new URL(URL_FILE_UPLOAD);\n        return uploadAnyFile(url, filename, is, password);\n    }\n","sourceCodeStart":152,"sourceCodeEnd":188,"githubUrl":"https://github.com/MuntashirAkon/AppManager/blob/0152f468fc9463ee02dc2ca83f6fe4989a2c4ca5/app/src/main/java/io/github/muntashirakon/AppManager/scanner/vt/VirusTotal.java#L152-L188","documentation":"VirusTotal file-report/scan request completed but the API returned an error payload instead of a scan report. fetchFileReportOrScan unwraps ResponseV3 and, when there is no response object but an error string, throws IOException(\"Scan error: \" + error) to surface the remote failure to the caller.","triggerScenarios":"Calling fetchFileReportOrScan when the VirusTotal v3 API responds with an error: rate limit exceeded, invalid/expired API key, file not yet analyzed and no scan queued, or a network/HTTP failure captured into responseReport.error.","commonSituations":"Free-tier API quota exhausted (HTTP 429), wrong VT API key, polling too early before the waitDuration elapses, transient VirusTotal outages.","solutions":["Inspect responseReport.error in the message to identify the remote cause (rate limit vs auth vs not-found).","Verify the VirusTotal API key is valid and has remaining quota.","Respect the waitDuration (30s) and retry with backoff when the error indicates rate limiting or pending analysis.","Catch the IOException at the call site and surface a user-visible failure instead of crashing."],"exampleFix":"// before\nthrow new IOException(\"Scan error: \" + responseReport.error);\n// after\nif (responseReport.error != null && responseReport.error.contains(\"rate limit\")) {\n    throw new IOException(\"VirusTotal rate limit exceeded, try again later\");\n}\nthrow new IOException(\"Scan error: \" + responseReport.error);","handlingStrategy":"try-catch","validationCode":"if (apiKey == null || apiKey.isEmpty()) throw new IllegalStateException(\"VT API key missing\");\nif (responseReport.error != null && !responseReport.error.isEmpty()) {\n    // error already known before unwrapping: handle manually\n}","typeGuard":null,"tryCatchPattern":"try {\n    virusTotal.fetchFileReportOrScan(file, waitDuration, response);\n} catch (IOException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"rate limit\")) {\n        // back off and retry later\n    } else {\n        // show scan-unavailable state to user\n    }\n}","preventionTips":["Validate the VT API key before enqueueing scans.","Respect the 30s waitDuration and free-tier rate limits (~4 req/min).","Always handle IOException from fetchFileReportOrScan; VT is a remote service that can fail.","Inspect responseReport.error strings to distinguish quota/auth/parse failures."],"tags":["network","virustotal","api","io"],"backgroundTag":"api-error-response","analyzedSha":"0152f468fc9463ee02dc2ca83f6fe4989a2c4ca5","analyzedAt":"2026-09-12T14:03:37.243Z","contentChangedAt":"2026-09-12T14:03:37.243Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}