siyuan-note/siyuan · error
tool handler unavailable
Error message
tool handler unavailable: %s
What it means
A tool was found by name, but its definition carries no executable entry point: both ContextHandler and Handler are nil. validateToolCallInput rejects such tools because the call could never be executed, returning this error instead of failing later mid-execution.
Solutions
- Fix the tool registration so a valid Handler or ContextHandler is always set
- Unregister tools entirely when their backing capability is unavailable instead of leaving handler-less definitions
- Add an init-time assertion that every registered tool has at least one handler
Example fix
// before: registered without a handler
tools.Register(&tools.Tool{Name: "export", Schema: schema})
// after
tools.Register(&tools.Tool{Name: "export", Schema: schema, Handler: exportHandler}) Defensive patterns
Strategy: validation
Validate before calling
for _, t := range registeredTools {
if t.Handler == nil && t.ContextHandler == nil {
return fmt.Errorf("tool %q registered without a handler", t.Name)
}
} Type guard
func hasHandler(t *tools.Tool) bool {
return t != nil && (t.Handler != nil || t.ContextHandler != nil)
} Try / catch
if err != nil && strings.Contains(err.Error(), "tool handler unavailable") {
return fmt.Errorf("tool %q is registered but has no executable handler; fix registration", toolName)
} Prevention
- Assert at registration time that every tool has a handler
- Unregister tools whose capability is disabled instead of nulling handlers
- Cover tool registration with an init-time unit test
When it happens
Trigger: Registering a Tool struct with only metadata (name/schema) but no Handler or ContextHandler; a code path that clears handlers conditionally (e.g. capability disabled) without unregistering the tool.
Common situations: Feature flags disabling a capability while leaving its tool registered; partially initialized tool registrations during startup; copy-pasted tool definitions missing the handler field.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- invalid tool arguments
- tool execution was cancelled before it started
- tool handler is not configured
- unknown tool
- agent compaction summary is empty
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/047bb8bf95e7395e.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/agent/tools.go:45
"github.com/siyuan-note/siyuan/kernel/mcp/tools"
kernelModel "github.com/siyuan-note/siyuan/kernel/model"
)
type executedToolResult struct {
Text string
ModelAttachments []tools.ModelAttachment
IsError bool
ExecutionUnknown bool
}
// validateToolCallInput 在确认和快照之前校验工具调用,避免无效调用被误判为写操作。
func validateToolCallInput(ctx context.Context, toolName string, args map[string]any) (*tools.Tool, *tools.ToolValidator, error) {
t, validator := tools.LookupToolWithValidator(toolName)
if t == nil {
return nil, nil, fmt.Errorf("unknown tool: %s", toolName)
}
if t.ContextHandler == nil && t.Handler == nil {
return nil, nil, fmt.Errorf("tool handler unavailable: %s", toolName)
}
if ctx.Err() != nil {
return nil, nil, fmt.Errorf("tool execution was cancelled before it started")
}
if err := validator.ValidateInputContext(ctx, args); err != nil {
return nil, nil, fmt.Errorf("invalid tool arguments: %w", err)
}
return t, validator, nil
}
func validateCapabilityCall(ctx context.Context, registration *capabilityRegistration, args map[string]any) error {
if registration == nil {
return fmt.Errorf("capability was not exposed in this model round")
}
if !capabilityStillExecutable(registration, args) {
return fmt.Errorf("capability is disabled or no longer available: %s", registration.ID)
}
if ctx.Err() != nil {View on GitHub (pinned to 9f775e8a12)