{"record":{"id":"f793977c0da11e9c","repo":"windmill-labs/windmill","slug":"e-instanceof-error-e-message-string-e","errorCode":null,"errorMessage":"${e instanceof Error ? e.message : String(e)}","messagePattern":"\\$\\{e instanceof Error \\? e\\.message : String\\(e\\)\\}","errorType":"exception","errorClass":"FileReadError","httpStatus":null,"severity":"error","filePath":"frontend/src/lib/components/copilot/chat/files/fileEngine.ts","lineNumber":138,"sourceCode":"\tlet end = requestedEnd\n\tif (end < start) end = start\n\tconst cappedByLines = end - start + 1 > maxLines\n\tif (cappedByLines) end = start + maxLines - 1\n\tif (end > totalLines) end = totalLines\n\n\tconst byteStart = entry.lineIndex[start - 1]\n\tconst byteEnd = end < totalLines ? entry.lineIndex[end] : entry.file.size\n\t// Bound the decode for newline-sparse files (minified JS, single-line JSONL): the\n\t// window can span the whole file, but we only ever return maxChars characters, and\n\t// a UTF-8 character is at most 4 bytes — so never materialize more than that.\n\tconst byteCap = byteStart + maxChars * 4\n\tconst byteCapped = byteCap < byteEnd\n\n\tlet text: string\n\ttry {\n\t\ttext = await entry.file.slice(byteStart, byteCapped ? byteCap : byteEnd).text()\n\t} catch (e) {\n\t\tthrow new FileReadError(entry.name, e instanceof Error ? e.message : String(e))\n\t}\n\n\tlet cappedByChars = byteCapped\n\tif (text.length > maxChars) {\n\t\ttext = text.slice(0, maxChars)\n\t\tcappedByChars = true\n\t}\n\n\t// When the char cap truncates the window short of `end`, the text holds fewer lines\n\t// than requested — so the note must report the last line actually returned and resume\n\t// at the next unread one (otherwise it claims lines it didn't return and skips them).\n\tlet lastLine = end\n\tlet resumeAt: number | undefined = end < totalLines ? end + 1 : undefined\n\tif (cappedByChars) {\n\t\tconst completeLines = (text.match(/\\n/g) || []).length\n\t\tif (completeLines >= 1) {\n\t\t\t// lines start..start+completeLines-1 are whole; the next line was cut mid-content.\n\t\t\t// Trim that partial line off the returned text so the body matches the note (and","sourceCodeStart":120,"sourceCodeEnd":156,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/frontend/src/lib/components/copilot/chat/files/fileEngine.ts#L120-L156","documentation":"FileReadError wraps any failure raised by Blob.slice(...).text() when the fileEngine reads a portion of a chat file entry. The original exception message is embedded ('Invalid AI agent provider configuration' style: 'FileReadError: <name>: <reason>'), so the underlying cause (decode failure, file handle gone, quota) is preserved in e.message.","triggerScenarios":"entry.file.slice(byteStart, byteCapped ? byteCap : byteEnd).text() rejects — e.g. the underlying File/Blob handle is stale after the source file changed on disk, the slice range is invalid, or the browser fails to read the bytes.","commonSituations":"Reading a file whose backing File object came from a File System Access API handle that was revoked; a truncated or locked file; browser storage eviction invalidating the handle; slicing beyond blob size.","solutions":["Re-acquire the file entry (re-pick or re-open the file) to get a fresh File handle, then retry.","Clamp byteStart/byteEnd/byteCap to entry.file.size before slicing to avoid out-of-range slices.","Catch FileReadError and fall back to asking the user to re-upload or re-attach the file."],"exampleFix":"// before\nconst text = await entry.file.slice(start, end).text()\n// after\nlet text\ntry {\n  text = await entry.file.slice(Math.min(start, entry.file.size), Math.min(end, entry.file.size)).text()\n} catch (e) {\n  throw new FileReadError(entry.name, e instanceof Error ? e.message : String(e))\n}","handlingStrategy":"try-catch","validationCode":"if (entry.file.size === 0) throw new FileReadError(entry.name, 'file is empty')\nif (byteEnd > entry.file.size) byteEnd = entry.file.size","typeGuard":"function isReadableFileEntry(entry: unknown): entry is { name: string; file: File } {\n  return typeof entry === 'object' && entry !== null &&\n    'file' in entry && entry.file instanceof File && entry.file.size > 0\n}","tryCatchPattern":"try {\n  text = await entry.file.slice(start, end).text()\n} catch (e) {\n  if (e instanceof FileReadError) {\n    return reAcquireAndRetry(entry.name)\n  }\n  throw e\n}","preventionTips":["Clamp slice ranges to the blob size before reading.","Re-validate that the File handle is still live (size > 0) before reading.","Fall back to re-acquiring the file from the user when a read fails."],"tags":["filesystem","blob","browser","read-error"],"backgroundTag":"file-read-failed","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}