sinelaw/fresh · error
JS error in
Error message
JS error in {}: {} What it means
Formatting step of format_js_error for QuickJS exceptions that carry a non-empty stack trace: it produces an anyhow error embedding the source name, error name, message, and full stack. It fires whenever a JS exception propagates out of evaluated plugin code (uncaught throw, runtime TypeError, explicit Error thrown by the plugin) and the engine attached trace info; exceptions without a stack take the shorter message-only branch. log_js_error and execute_js use the result to report the plugin failure.
Solutions
- Change the plugin to throw Error objects instead of primitives so messages are readable
- Inspect the coerced value in the message to identify the throw site
- Wrap plugin entry points in try/catch that converts thrown values to Errors
Example fix
// before
throw 42;
// after
throw new Error('failed with code 42'); Defensive patterns
Strategy: try-catch
Validate before calling
// static check: no bare 'throw <non-Error>' in plugin sources
grep -nE "throw\s+(['\"0-9{])" plugin.js Try / catch
match backend.execute_js(src) {
Err(e) if e.to_string().contains("JS error in") => {
log::warn!("plugin threw a non-Error value: {e}");
disable_plugin();
}
other => other,
} Prevention
- Enforce an ESLint rule (no-throw-literal) in plugin codebases
- Wrap third-party plugin entry points to normalize thrown values into Errors
- Review plugin code for raw throw statements
- Add CI tests that exercise plugin error paths
When it happens
Trigger: Plugin code throws a non-Error value such as a string, number, or object (e.g. throw "oops") during execute_js; the value cannot be converted to a JS string so it is Debug-formatted.
Common situations: Plugins throwing raw strings or numbers instead of Error objects; third-party plugin code with unconventional throw patterns.
Related errors
- JS error in : : Stack trace
- JS error in
- editor.spawnProcess is not implemented (missing…
- editor.spawnHostProcess is not implemented (missing…
- Cannot resolve import
AI-assisted analysis of sinelaw/fresh@67894ca546 (2026-09-13).
Data as JSON: /api/errors/eac481b94ff91853.
Report an issue: GitHub.
Appendix: source
Thrown at crates/fresh-plugin-runtime/src/backend/quickjs_backend.rs:428
if !stack.is_empty() {
return anyhow::anyhow!(
"JS error in {}: {}: {}\nStack trace:\n{}",
source_name,
name,
message,
stack
);
} else {
return anyhow::anyhow!("JS error in {}: {}: {}", source_name, name, message);
}
} else {
// Exception is not an object, try to convert to string
let exc_str: String = exc
.as_string()
.and_then(|s: &rquickjs::String| s.to_string().ok())
.unwrap_or_else(|| format!("{:?}", exc));
return anyhow::anyhow!("JS error in {}: {}", source_name, exc_str);
}
}
}
// Fall back to the basic error message
anyhow::anyhow!("JS error in {}: {}", source_name, err)
}
/// Log a JavaScript error with full details
/// If panic_on_js_errors is enabled, this will panic to surface JS errors immediately
/// A JS expression reading `name` off `globalThis`, as a quoted key.
///
/// Handler names are strings a plugin chose, and plugin ids routinely carry
/// characters that are not JS identifiers — every Finder-based plugin
/// registers handlers like `_finder_git-grep_preview_tick`. Interpolated
/// after a dot, that name is not a lookup but an expression (`git` minus
/// `grep_preview_tick`), and the call dies with a ReferenceError naming a
/// function nobody wrote. Quoting the key makes any name work, includingView on GitHub (pinned to 67894ca546)