{"record":{"id":"d86af1fbb91a5c4c","repo":"OtterMind/Chat2DB","slug":"ai-attachment-localparsefailed","errorCode":"ai.attachment.localParseFailed","errorMessage":"ai.attachment.localParseFailed","messagePattern":"ai\\.attachment\\.localParseFailed","errorType":"exception","errorClass":"BusinessException","httpStatus":null,"severity":"error","filePath":"chat2db-community-server/chat2db-community-domain/chat2db-community-domain-core/src/main/java/ai/chat2db/community/domain/core/impl/ai/AiAttachmentServiceImpl.java","lineNumber":78,"sourceCode":"            return parse(fileName, inputStream);\n        } catch (IOException e) {\n            throw new BusinessException(\"ai.attachment.parseFailed\", new Object[]{fileName, e.getMessage()}, e);\n        }\n    }\n\n    public ChatAttachment parse(AiLocalAttachmentParseRequest param) {\n        if (param == null || StringUtils.isBlank(param.getFilePath())) {\n            throw new BusinessException(\"ai.attachment.filePathRequired\");\n        }\n        File file = new File(param.getFilePath().trim());\n        if (!file.exists() || !file.isFile()) {\n            throw new BusinessException(\"ai.attachment.fileNotFound\");\n        }\n        String fileName = StringUtils.defaultIfBlank(param.getFileName(), file.getName());\n        try (InputStream inputStream = new FileInputStream(file)) {\n            return parse(fileName, inputStream);\n        } catch (IOException e) {\n            throw new BusinessException(\"ai.attachment.localParseFailed\", new Object[]{file.getPath(), e.getMessage()}, e);\n        }\n    }\n\n    public String buildStructuredContext(List<ChatAttachment> attachments) {\n        if (CollectionUtils.isEmpty(attachments)) {\n            return null;\n        }\n        StringBuilder builder = new StringBuilder(4096);\n        builder.append(\"Uploaded file context for the current conversation. \");\n        builder.append(\"Treat it as user-provided evidence. \");\n        builder.append(\"If the parsed content is truncated, say so when it affects certainty.\\n\");\n\n        int remaining = MAX_CONTEXT_LENGTH;\n        int index = 1;\n        for (ChatAttachment attachment : attachments) {\n            if (attachment == null || StringUtils.isBlank(attachment.getContent())) {\n                continue;\n            }","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/OtterMind/Chat2DB/blob/5ee1e990e73fbcae1969dc554be254fedb3ab888/chat2db-community-server/chat2db-community-domain/chat2db-community-domain-core/src/main/java/ai/chat2db/community/domain/core/impl/ai/AiAttachmentServiceImpl.java#L60-L96","documentation":"Thrown by AiAttachmentServiceImpl.parse(AiLocalAttachmentParseRequest) inside the catch(IOException) around `new FileInputStream(file)` and the delegated parse(fileName, inputStream). Args are {file.getPath(), e.getMessage()}. The i18n message resolves to 'Failed to parse local file {0}: {1}'. It surfaces low-level read/parse failures for a local file that existed at the existence check.","triggerScenarios":"The file passed the exists/isFile check but opening or reading it raised IOException: file deleted between check and open (TOCTOU), read permission denied at open time, file locked exclusively by another process, or the underlying document parser (PDF/DOCX/XLSX) threw IOException on a corrupt or truncated payload.","commonSituations":"Concurrent cleanup of the temp dir racing the parse; an antivirus or backup process holding an exclusive lock on Windows; a truncated upload (connection drop) producing a corrupt zip-based docx/xlsx; permissions changed after the existence check.","solutions":["Inspect the embedded e.getMessage() / cause to tell a missing-file race (re-upload) from a corrupt-payload parse failure (reject the file).","Ensure the temp upload is not deleted until the parse completes; copy the uploaded bytes to a stable path owned by the parse lifecycle.","On Windows, confirm no other process holds an exclusive lock; run the service with read permission on the upload dir.","Guard against TOCTOU by re-validating Files.isReadable(file) immediately before opening the stream."],"exampleFix":"// before\ntry (InputStream in = new FileInputStream(file)) {\n    return parse(name, in);\n} catch (IOException e) { /* wraps as localParseFailed */ }\n\n// after - re-check readability and copy to a stable path\nif (!Files.isReadable(file.toPath())) {\n    throw new BusinessException(\"ai.attachment.fileNotFound\");\n}\nPath stable = Files.copy(file.toPath(), tempDir.resolve(UUID.randomUUID() + \"-\" + file.getName()));\ntry (InputStream in = Files.newInputStream(stable)) {\n    return parse(name, in);\n}","handlingStrategy":"try-catch","validationCode":"File f = new File(param.getFilePath().trim());\nif (!f.isFile() || !Files.isReadable(f.toPath())) {\n    return ResponseEntity.badRequest().body(\"file missing or unreadable\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    service.parse(param);\n} catch (BusinessException e) {\n    if (\"ai.attachment.localParseFailed\".equals(e.getCode())) {\n        // args[0]=path, args[1]=root message\n        return ResponseEntity.status(422).body(\"could not parse file: \" + e.getArgs()[1]);\n    }\n    throw e;\n}","preventionTips":["Copy uploaded bytes to a stable path owned by the parse before opening.","Guard against TOCTOU with Files.isReadable immediately before opening.","On Windows, ensure no process holds an exclusive lock on the file."],"tags":["ai","attachment","io","filesystem","parsing"],"backgroundTag":null,"analyzedSha":"5ee1e990e73fbcae1969dc554be254fedb3ab888","analyzedAt":"2026-08-14T07:05:03.077Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}