{"record":{"id":"ad292345c10b8a97","repo":"tailwindlabs/tailwindcss","slug":"failed-to-read-file","errorCode":null,"errorMessage":"Failed to read file","messagePattern":"Failed to read file","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/node/src/lib.rs","lineNumber":141,"sourceCode":"  #[napi]\n  pub fn scan(&mut self) -> Vec<String> {\n    self.scanner.scan()\n  }\n\n  #[napi]\n  pub fn scan_files(&mut self, input: Vec<ChangedContent>) -> Vec<String> {\n    self\n      .scanner\n      .scan_content(input.into_iter().map(Into::into).collect())\n  }\n\n  #[napi]\n  pub fn get_candidates_with_positions(\n    &mut self,\n    input: ChangedContent,\n  ) -> Vec<CandidateWithPosition> {\n    let content = input.content.unwrap_or_else(|| {\n      std::fs::read_to_string(input.file.unwrap()).expect(\"Failed to read file\")\n    });\n\n    let input = ChangedContent {\n      file: None,\n      content: Some(content.clone()),\n      extension: input.extension,\n    };\n\n    let mut utf16_idx = IndexConverter::new(&content[..]);\n\n    self\n      .scanner\n      .get_candidates_with_positions(input.into())\n      .into_iter()\n      .map(|(candidate, position)| CandidateWithPosition {\n        candidate,\n        position: utf16_idx.get(position),\n      })","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/tailwindlabs/tailwindcss/blob/16e94cbf7f965c5ad697e90e940b5e178efad67c/crates/node/src/lib.rs#L123-L159","documentation":"Panics (via .expect) in the napi binding get_candidates_with_positions when ChangedContent.file is set but std::fs::read_to_string fails to read it (file missing, unreadable, or a permission error). This is the Node-facing wrapper around the Rust scanner; when content is None it falls back to reading the file path and unwinds on any IO error.","triggerScenarios":"Calling the native get_candidates_with_positions with a ChangedContent whose file path does not exist, is not readable, or is a directory. The .expect() turns the IO error into a panic that crosses the napi boundary.","commonSituations":"Passing a stale or deleted file path to the scanner. Path resolution mismatch (relative vs absolute) between the JS caller and the working directory. Permission errors in restricted environments. Race where the file is deleted between scan scheduling and read.","solutions":["Ensure the file path exists and is readable before calling: use fs.existsSync / fs.accessSync.","Pass content directly in ChangedContent.content (with extension set) instead of a file path, reading it on the JS side with error handling.","Validate paths and filter out missing files before submitting to the native scanner."],"exampleFix":"// before — panics if file is gone\nscanner.getCandidatesWithPositions({ file: path })\n\n// after\nconst content = fs.readFileSync(path, 'utf8')\nscanner.getCandidatesWithPositions({ content, extension: 'html' })","handlingStrategy":"validation","validationCode":"// JS side: verify file exists and is readable before the native call\nconst fs = require('fs')\nif (input.file && (!fs.existsSync(input.file) || !fs.statSync(input.file).isFile())) {\n  throw new Error(`Cannot read candidate source: ${input.file}`)\n}\nscanner.getCandidatesWithPositions(input)","typeGuard":"function isReadableFile(file: string): boolean {\n  try {\n    return fs.statSync(file).isFile()\n  } catch {\n    return false\n  }\n}","tryCatchPattern":"// Prefer passing content to avoid the native panic\nlet content\ntry {\n  content = fs.readFileSync(input.file, 'utf8')\n} catch (e) {\n  console.warn(`skipping unreadable file: ${input.file}`)\n  return []\n}\nscanner.getCandidatesWithPositions({ content, extension: input.extension })","preventionTips":["Prefer passing content (read with error handling on the JS side) over file paths.","Validate file existence and readability before handing paths to the native scanner.","Filter deleted/unreadable files from the candidate list up front."],"tags":["tailwindcss","napi","rust","node","panic","filesystem"],"backgroundTag":null,"analyzedSha":"16e94cbf7f965c5ad697e90e940b5e178efad67c","analyzedAt":"2026-08-12T06:02:42.469Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}