fix(api): return user-safe processing errors, keep raw stderr in logs

Add friendlyError() which collapses raw external-tool failure output (ffmpeg/ffprobe/LibreOffice/qpdf/etc.) into one generic sentence while preserving intentional validation messages and scrubbing internal paths. Apply it at every client-facing error surface in the tool factory and job worker (sync 422, async SSE, pipeline + batch finalize). The full error is still recorded server-side via request.log.error / logger.error and telemetry.
This commit is contained in:
SnapOtter
2026-06-17 14:22:16 +08:00
parent e96c314ab9
commit 4af4bfa8eb
3 changed files with 42 additions and 10 deletions
+23
View File
@@ -13,3 +13,26 @@ export function formatZodErrors(issues: ZodIssue[]): string {
export function stripInternalPaths(message: string): string {
return message.replace(/\/(tmp|data|app|opt|home|workspace)\b[^\s'")}]*/g, "[internal]");
}
/**
* Markers of a raw external-tool failure dump (ffmpeg/ffprobe/libvpx/x264/
* LibreOffice/ghostscript/qpdf/python traceback). These messages are meant for
* server logs, never for end users.
*/
const RAW_TOOL_FAILURE =
/ffmpeg exited|ffprobe|conversion failed|libvpx|x26[45] \[|stream mapping|pixel format|could not open encoder|segmentation fault|core dump|traceback \(most recent|gs: |libreoffice|qpdf:/i;
/**
* Produce a user-safe error detail. Intentional validation messages (short,
* single-line) pass through unchanged after path scrubbing; raw tool-runner
* dumps collapse to one generic sentence. The full error is still recorded in
* server logs and telemetry by the caller -- only the client-facing string is
* sanitized. Idempotent, so it is safe to apply at every error surface.
*/
export function friendlyError(message: string): string {
const cleaned = stripInternalPaths(message);
if (RAW_TOOL_FAILURE.test(cleaned) || cleaned.length > 280 || cleaned.split("\n").length > 3) {
return "Processing failed. The file may be in an unsupported or corrupted format.";
}
return cleaned;
}