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

  1. Change the plugin to throw Error objects instead of primitives so messages are readable
  2. Inspect the coerced value in the message to identify the throw site
  3. 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

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


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, including

View on GitHub (pinned to 67894ca546)