sinelaw/fresh · error

JS error in

Error message

JS error in {}: {}: {}

What it means

Formatting step of format_js_error for QuickJS exceptions that are objects: it extracts the error's name, message, and stack from the exception object and builds an anyhow error. This variant fires when a stack trace is absent or empty (e.g. thrown non-Error values like strings/objects lacking `.stack`, or engine errors without trace info); the with-stack variant at the same site is used otherwise. Callers log_js_error and execute_js surface it to the plugin host as the failure reason.

Solutions

  1. Fix the JS error named in the message (e.g. TypeError: undefined is not a function)
  2. Add stack capture by throwing real Error objects in plugin code
  3. Log inputs around the throwing call site to reproduce
  4. Disable code minification for plugin bundles to retain stacks

Example fix

// before
throw 'bad input';
// after
throw new Error('bad input: ' + JSON.stringify(input));
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure plugin modules only throw Error instances
function assertError(e) { if (!(e instanceof Error)) throw new Error(String(e)); }

Try / catch

if let Err(e) = backend.execute_js(src) {
    let msg = e.to_string();
    if msg.starts_with("JS error in") {
        eprintln!("plugin JS failure (no stack): {msg}");
    }
}

Prevention

When it happens

Trigger: Plugin code throws a value whose 'stack' property is empty/absent (e.g. manually constructed Error without stack, or engine-produced exception lacking stack) during JS execution.

Common situations: Throwing plain objects or Errors with stack suppressed in plugins; minified/bundled plugin code stripping stack info; older QuickJS builds not populating stack.

Related errors


AI-assisted analysis of sinelaw/fresh@67894ca546 (2026-09-13). Data as JSON: /api/errors/edf7b1018157ee22. Report an issue: GitHub.

Appendix: source

Thrown at crates/fresh-plugin-runtime/src/backend/quickjs_backend.rs:420

            if let Some(exc_obj) = exc.as_object() {
                let message: String = exc_obj
                    .get::<_, String>("message")
                    .unwrap_or_else(|_| "Unknown error".to_string());
                let stack: String = exc_obj.get::<_, String>("stack").unwrap_or_default();
                let name: String = exc_obj
                    .get::<_, String>("name")
                    .unwrap_or_else(|_| "Error".to_string());

                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

View on GitHub (pinned to 67894ca546)