OtterMind/Chat2DB · error · BusinessException

ai.attachment.localParseFailed

ai.attachment.localParseFailed

Error message

ai.attachment.localParseFailed

What it means

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.

Source

Thrown at chat2db-community-server/chat2db-community-domain/chat2db-community-domain-core/src/main/java/ai/chat2db/community/domain/core/impl/ai/AiAttachmentServiceImpl.java:78

            return parse(fileName, inputStream);
        } catch (IOException e) {
            throw new BusinessException("ai.attachment.parseFailed", new Object[]{fileName, e.getMessage()}, e);
        }
    }

    public ChatAttachment parse(AiLocalAttachmentParseRequest param) {
        if (param == null || StringUtils.isBlank(param.getFilePath())) {
            throw new BusinessException("ai.attachment.filePathRequired");
        }
        File file = new File(param.getFilePath().trim());
        if (!file.exists() || !file.isFile()) {
            throw new BusinessException("ai.attachment.fileNotFound");
        }
        String fileName = StringUtils.defaultIfBlank(param.getFileName(), file.getName());
        try (InputStream inputStream = new FileInputStream(file)) {
            return parse(fileName, inputStream);
        } catch (IOException e) {
            throw new BusinessException("ai.attachment.localParseFailed", new Object[]{file.getPath(), e.getMessage()}, e);
        }
    }

    public String buildStructuredContext(List<ChatAttachment> attachments) {
        if (CollectionUtils.isEmpty(attachments)) {
            return null;
        }
        StringBuilder builder = new StringBuilder(4096);
        builder.append("Uploaded file context for the current conversation. ");
        builder.append("Treat it as user-provided evidence. ");
        builder.append("If the parsed content is truncated, say so when it affects certainty.\n");

        int remaining = MAX_CONTEXT_LENGTH;
        int index = 1;
        for (ChatAttachment attachment : attachments) {
            if (attachment == null || StringUtils.isBlank(attachment.getContent())) {
                continue;
            }

View on GitHub (pinned to 5ee1e990e7)

Solutions

  1. Inspect the embedded e.getMessage() / cause to tell a missing-file race (re-upload) from a corrupt-payload parse failure (reject the file).
  2. Ensure the temp upload is not deleted until the parse completes; copy the uploaded bytes to a stable path owned by the parse lifecycle.
  3. On Windows, confirm no other process holds an exclusive lock; run the service with read permission on the upload dir.
  4. Guard against TOCTOU by re-validating Files.isReadable(file) immediately before opening the stream.

Example fix

// before
try (InputStream in = new FileInputStream(file)) {
    return parse(name, in);
} catch (IOException e) { /* wraps as localParseFailed */ }

// after - re-check readability and copy to a stable path
if (!Files.isReadable(file.toPath())) {
    throw new BusinessException("ai.attachment.fileNotFound");
}
Path stable = Files.copy(file.toPath(), tempDir.resolve(UUID.randomUUID() + "-" + file.getName()));
try (InputStream in = Files.newInputStream(stable)) {
    return parse(name, in);
}
Defensive patterns

Strategy: try-catch

Validate before calling

File f = new File(param.getFilePath().trim());
if (!f.isFile() || !Files.isReadable(f.toPath())) {
    return ResponseEntity.badRequest().body("file missing or unreadable");
}

Try / catch

try {
    service.parse(param);
} catch (BusinessException e) {
    if ("ai.attachment.localParseFailed".equals(e.getCode())) {
        // args[0]=path, args[1]=root message
        return ResponseEntity.status(422).body("could not parse file: " + e.getArgs()[1]);
    }
    throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of OtterMind/Chat2DB@5ee1e990e7 (2026-08-14). Data as JSON: /api/errors/d86af1fbb91a5c4c. Report an issue: GitHub.