sinelaw/fresh · error
Invalid path encoding
Error message
Invalid path encoding
What it means
This error is raised when a prepared TypeScript plugin's file path cannot be converted from an OsStr to a valid UTF-8 Rust string via `Path::to_str()`. The runtime needs the path as a `&str` to pass into `execute_js` (typically for `import()` resolution / stack traces inside QuickJS), and non-UTF-8 paths would be silently mangled, so the code fails fast instead. It is a fail-fast guard against OS paths containing invalid Unicode.
Solutions
- Rename the plugin file and its parent directories to use only valid UTF-8 (ASCII) characters.
- Check the path with `locale`/`ls` on the host to find the offending filename bytes and fix the mount or filesystem encoding.
- If programmatic control is needed, sanitize paths before handing them to the plugin loader, or re-encode with `String::from_utf8_lossy` only if the resulting path is still resolvable.
- On Windows, ensure the path does not contain characters outside UTF-16-to-UTF-8 representable ranges; move plugins to a simple ASCII path.
Example fix
// before plugins-dir/caf�-plugins/my-plugin.ts (raw non-UTF-8 byte in dir name) // after plugins-dir/cafe-plugins/my-plugin.ts
Defensive patterns
Strategy: validation
Validate before calling
fn is_utf8_path(p: &std::path::Path) -> bool {
p.to_str().is_some()
}
// call before loading:
if !is_utf8_path(&prepared.path) {
eprintln!("skip plugin: path is not valid UTF-8: {:?}", prepared.path);
} Type guard
fn valid_utf8_path(p: &std::path::Path) -> Option<&str> {
p.to_str()
} Try / catch
match load_result {
Err(e) if e.to_string().contains("Invalid path encoding") => {
eprintln!("Plugin skipped: file path is not valid UTF-8; rename the file/directory.");
}
Err(e) => return Err(e),
Ok(v) => v,
} Prevention
- Keep plugin directories and filenames ASCII/UTF-8 only.
- Sanitize paths from user config before passing them to the loader.
- When copying plugins from archives or network shares, verify filenames decode as UTF-8.
- Log `path.as_os_str()` when a load fails to spot non-UTF-8 bytes early.
When it happens
Trigger: Calling the plugin-loading flow (which prepares a plugin and calls `execute_js(&prepared.js_code, path_str)`) with a plugin file whose absolute path contains bytes that are not valid UTF-8. This happens right after 'Loaded i18n strings for plugin ...' during plugin load.
Common situations: Plugin directories or filenames created with non-UTF-8 encodings (e.g. Latin-1 filenames from old Samba/NFS mounts, filenames with raw 0x80-0xFF bytes on Linux); plugins installed in directories whose names contain such bytes; moving plugin caches across systems with different filename encodings.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- Failed to read plugin
- project path does not exist
- ( MB) requires full load. (l)oad, (e)ncoding, (C)ancel?
- buffer.cannot_open_directory
- Buffer has no file path
AI-assisted analysis of sinelaw/fresh@67894ca546 (2026-09-13).
Data as JSON: /api/errors/b61bdeefb4360a3a.
Report an issue: GitHub.
Appendix: source
Thrown at crates/fresh-plugin-runtime/src/thread.rs:1530
/// must run on the plugin thread.
fn execute_prepared_plugin(
runtime: &Rc<RefCell<QuickJsBackend>>,
plugins: &mut HashMap<String, TsPluginInfo>,
prepared: &PreparedPlugin,
) -> Result<()> {
// Register i18n strings
if let Some(ref i18n) = prepared.i18n {
runtime
.borrow_mut()
.services
.register_plugin_strings(&prepared.name, i18n.clone());
tracing::debug!("Loaded i18n strings for plugin '{}'", prepared.name);
}
let path_str = prepared
.path
.to_str()
.ok_or_else(|| anyhow!("Invalid path encoding"))?;
let exec_start = std::time::Instant::now();
runtime
.borrow_mut()
.execute_js(&prepared.js_code, path_str)?;
let exec_elapsed = exec_start.elapsed();
tracing::debug!(
"execute_prepared_plugin: plugin '{}' executed in {:?}",
prepared.name,
exec_elapsed
);
plugins.insert(
prepared.name.clone(),
TsPluginInfo {
name: prepared.name.clone(),
path: prepared.path.clone(),View on GitHub (pinned to 67894ca546)