Hmbown/CodeWhale · error · io::Error

{error}

Error message

{error}

What it means

read_workspace_text converts any non-specific I/O failure from the bounded workspace read into io::Error::other, preserving the underlying error as its source. The message is the inner error's Display output. It wraps everything after the size-cap check, including the file open and UTF-8 conversion steps surfaced by the confined opener.

Solutions

  1. Read the inner source error: re-check the path exists, is a regular file, and is readable before calling.
  2. For non-UTF-8 files, convert or skip them — the reader only accepts valid UTF-8.
  3. Retry if the failure was a transient race (file being rewritten); otherwise exclude the path from LSP requests.

Example fix

// before
read_workspace_text(&maybe_missing_path)?;
// after
if path.is_file() { read_workspace_text(&path)? } else { eprintln!("skipping {}", path.display()) }
Defensive patterns

Strategy: try-catch

Validate before calling

if !path.is_file() { eprintln!("not a regular file: {path:?}"); }
if std::fs::read(path).map(|b| std::str::from_utf8(&b).is_ok()).unwrap_or(false) { /* ok */ }

Type guard

fn readable_utf8_file(p: &Path) -> bool {
    std::fs::read(p).map(|b| std::str::from_utf8(&b).is_ok()).unwrap_or(false)
}

Try / catch

match read_workspace_text(p) {
    Err(e) => { eprintln!("workspace read failed: {e} (source: {:?})", e.source()); fallback_text() }
    Ok(t) => use_text(t),
}

Prevention

When it happens

Trigger: read_workspace_text fails for a reason other than outside-workspace or size-cap: the no-follow open fails (file deleted mid-read, permission error, too many symlink levels, not-a-file), or from_utf8 fails producing an inner UTF-8 error.

Common situations: File removed or renamed between listing and reading; the entry is a directory or FIFO, not a regular file; a symlinked file inside the workspace rejected by the no-follow opener; a binary file passed where UTF-8 text is expected.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/143fb9c7e6d638ec. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/lsp/mod.rs:248

                        "LSP file is outside the workspace",
                    )
                })?;
            // Match the existing workspace file serving ceiling. Decode only
            // bounded UTF-8 bytes from the same no-follow workspace opener.
            const MAX_DOCUMENT_BYTES: u64 = 16 * 1024 * 1024;
            let file = crate::fleet::files::WorkspaceFile::open(&canonical_root, relative, false)?;
            let mut bytes = Vec::new();
            file.open_file()?
                .take(MAX_DOCUMENT_BYTES + 1)
                .read_to_end(&mut bytes)?;
            if bytes.len() as u64 > MAX_DOCUMENT_BYTES {
                return Err(std::io::Error::new(
                    std::io::ErrorKind::InvalidData,
                    "LSP file exceeds the document limit",
                ));
            }
            String::from_utf8(bytes)
                .map_err(|error| std::io::Error::new(std::io::ErrorKind::InvalidData, error))
        })
        .await
        .map_err(std::io::Error::other)?
    }

    /// Inject a fake transport for a language. Used by tests so we never
    /// fork a real LSP server in CI.
    #[cfg(test)]
    pub async fn install_test_transport(&self, lang: Language, transport: Arc<dyn LspTransport>) {
        self.test_transports.lock().await.insert(lang, transport);
    }

    /// Poll the LSP server for diagnostics on `file`. Returns the rendered
    /// [`DiagnosticBlock`] (already truncated to the configured per-file
    /// max) or `None` when the manager is disabled / has no server / the
    /// poll times out.
    ///
    /// The `_edit_seq` argument is currently a no-op; it exists in the

View on GitHub (pinned to 73e0f67d83)