Automattic/harper · error · anyhow::Error
Unable to convert URI to file path.
Error message
Unable to convert URI to file path.
What it means
harper-ls's `fileify_path` converts an LSP document URI into a filesystem-friendly file name (Neovim undo-file style) by calling `UriExt::to_file_path()` on the URI. That conversion only succeeds for URIs that actually denote local file paths (e.g. `file:///home/user/doc.md`). When the URI cannot be mapped to a file path — it is `None` — the function short-circuits with this anyhow error, since per-client persisted state (ignored lints, per-file dictionaries) would otherwise have nowhere well-defined to live.
Source
Thrown at harper-ls/src/io_utils.rs:14
use anyhow::anyhow;
use std::path::{Component, PathBuf};
use tower_lsp_server::{UriExt, lsp_types::Uri};
/// Rewrites a path to a filename using the same conventions as
/// [Neovim's undo-files](https://neovim.io/doc/user/options.html#'undodir').
pub fn fileify_path(uri: &Uri) -> anyhow::Result<PathBuf> {
let mut rewritten = String::new();
// We assume all URLs are local files and have a base.
for seg in uri
.to_file_path()
.ok_or_else(|| anyhow!("Unable to convert URI to file path."))?
.components()
{
if !matches!(seg, Component::RootDir) {
rewritten.push_str(&seg.as_os_str().to_string_lossy());
rewritten.push('%');
}
}
Ok(rewritten.into())
}
View on GitHub (pinned to 5fe7d5ab76)
Solutions
- Identify the URI in the failing editor notification; it is almost certainly non-`file:` (untitled/virtual/remote document).
- Guard in the caller: skip persistence for non-file URIs by checking the scheme (e.g. only proceed when the URI starts with `file:` or when `to_file_path()` succeeds) instead of propagating the error.
- If the document is `untitled:`, save it to disk first or configure the editor to assign a real path; harper-ls cannot derive an on-disk path otherwise.
- For remote/SSH setups, ensure the language server runs on the same host as the files so the client sends real `file:` URIs.
- If you control the code, fall back to a hash of the URI string when a file path cannot be derived, so state can still be persisted per-URI.
Example fix
// before
let path = fileify_path(&uri)?;
// after: skip non-file URIs instead of failing
let path = match uri.to_file_path() {
Some(_) => fileify_path(&uri)?,
None => return Ok(None), // untitled/virtual document: no on-disk state
}; Defensive patterns
Strategy: validation
Validate before calling
// Rust: validate the URI before calling fileify_path
fn is_local_file(uri: &lsp_types::Uri) -> bool {
uri.as_str().starts_with("file:") || uri.to_file_path().is_some()
}
if !is_local_file(&uri) {
// skip persistence / return early instead of erroring
} Type guard
// Rust: narrow an arbitrary Uri to a convertible file URI
fn as_file_path(uri: &Uri) -> Option<PathBuf> {
uri.to_file_path()
} Try / catch
// Rust (anyhow): match on the error and degrade gracefully
match fileify_path(&uri) {
Ok(p) => get_ignored_lints_path(&p),
Err(e) if e.to_string().contains("Unable to convert URI to file path") => {
// non-file URI: treat as no persisted state
eprintln!("skipping state for non-file URI: {uri}");
}
Err(e) => return Err(e),
} Prevention
- Check the URI scheme (`file:`) before any per-file persistence call in an LSP handler.
- Handle `untitled:` buffers explicitly — they never map to a file path.
- Run the language server on the same host/filesystem as the edited documents so clients send file URIs.
- Never assume all LSP URIs are local files despite the code comment; virtual schemes (jdt, zipfile, http) occur in practice.
- Log the offending URI string with the error to make client-side diagnosis immediate.
When it happens
Trigger: Calling `fileify_path` (directly or indirectly via `get_ignored_lints_path` / `get_file_dict_path`) with an LSP `Uri` whose scheme is not `file:` — e.g. `untitled:` buffers, `http(s)://` remote documents, `jdt://`/`zipfile://` virtual schemes, or a malformed URI that `to_file_path()` cannot parse into a local path.
Common situations: A client sends `didOpen`/`didChange`/`codeAction` notifications for an unsaved scratch buffer (`untitled:Untitled-1`) or a remotely-hosted/virtual document (remote SSH, browser-based editors, container schemes). Editors like Neovim and VS Code emit such URIs for new or virtual files, and harper-ls then fails while resolving the per-file ignored-lints or dictionary path.
Understand the failure class
Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.
Related errors
- Unable to convert URL to file path.
- Unable to convert URI to file path.
- The code action configuration must be an object.
- ForceStable must be a boolean value.
- Settings must be an object.
AI-assisted analysis of Automattic/harper@5fe7d5ab76 (2026-09-06).
Data as JSON: /api/errors/8f3510fd843a01bd.
Report an issue: GitHub.