siyuan-note/siyuan · error

failed to run task on event loop

Error message

failed to run task on event loop

What it means

The plugin Worker schedules a task (TaskExecutor) on a JS event loop (e.g. a GJavaScript/v8-style loop for plugin runtime). After running the callback, the deferred result is checked; if the executor set a non-nil error, Run() collapses it into the generic sentinel "failed to run task on event loop". It means the task itself failed while executing inside the event loop, not that the loop could not be reached.

Source

Thrown at kernel/plugin/worker.go:74

				if r := recover(); r != nil {
					logging.LogErrorf("task callback panicked: %v\n%s", r, debug.Stack())
				}
			}()

			// 捕获执行器中的 panic 并保留原始调用栈,同时将错误传给回调。
			if r := recover(); r != nil {
				logging.LogErrorf("task executor panicked: %v\n%s", r, debug.Stack())
				err = fmt.Errorf("task executor panicked: %v", r)
			}
			if callback != nil {
				callback(rt, result, err)
			}
		}()

		result, err = executor(rt)
	})
	if !success {
		return fmt.Errorf("failed to run task on event loop")
	}
	return nil
}

func (w *Worker) RunSync(fn TaskExecutor) (result any, err error) {
	response := make(chan TaskResult, 1)
	err = w.Run(fn, func(rt *goja.Runtime, result any, err error) {
		response <- TaskResult{result, err}
	})

	if err != nil {
		close(response)
		return
	}

	r := <-response
	result = r.value
	err = r.err

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Inspect kernel/plugin logs just before this error for the underlying error the executor produced; the generic message discards the original cause, so check the plugin's own console/log output
  2. Verify the plugin runtime was fully initialized (runtime loaded, entry script executed) before invoking Run
  3. If you wrote the hook/capability, fix the error it returns (nil check all runtime API results)
  4. If the executor needs the original error surfaced, change Run/TaskExecutor plumbing to wrap `err` (e.g. fmt.Errorf("failed to run task on event loop: %w", err)) instead of dropping it

Example fix

// before
if !success {
	return fmt.Errorf("failed to run task on event loop")
}
// after
if !success {
	if err != nil {
		return fmt.Errorf("failed to run task on event loop: %w", err)
	}
	return fmt.Errorf("failed to run task on event loop")
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Go: check runtime readiness before dispatch
if w.runtime == nil || w.closed() {
	return errors.New("plugin runtime not ready")
}

Type guard

func (w *Worker) ready() bool { return w != nil && w.loop != nil }

Try / catch

if err := worker.Run(executor); err != nil {
	if strings.Contains(err.Error(), "failed to run task on event loop") {
		// fall back to sync path or log plugin-side error
	}
}

Prevention

When it happens

Trigger: Calling Worker.Run(executor) where the executor function returns a non-nil error (success flag false after `result, err = executor(rt)` in kernel/plugin/worker.go:74). Callers include invokeAgentCapability, invokeHook, callRpcMethod, runtimeEventHandler and the HTTP handler path.

Common situations: A plugin hook or agent capability panics or returns an error inside the JS runtime; a runtime (rt) is in a bad state (closed loop, missing bindings); RPC method invoked on a plugin whose runtime threw while handling the call.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/2c45fd2c5c8f8ac0. Report an issue: GitHub.