siyuan-note/siyuan · error
tool handler unavailable: %s
Error message
tool handler unavailable: %s
What it means
The tool was found in the registry by name, but both its ContextHandler and Handler fields are nil, so there is no code to execute it. validateToolCallInput refuses to continue because dispatching would panic or silently no-op. This catches registry entries that were registered as placeholders or whose handler wiring failed.
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 8641553a1f)
Solutions
- Assign a valid Handler (or ContextHandler) to the tool's tools.Tool definition in the registry.
- If the tool is intentionally unimplemented on this platform, remove it from the registry or filter it out of the list exposed to the model.
- Add a startup assertion/registration-time check that every registered tool has a handler.
Example fix
// before
var writeFile = &tools.Tool{Name: "writeFile", Description: "..."}
// after
var writeFile = &tools.Tool{Name: "writeFile", Description: "...", Handler: writeFileHandler} Defensive patterns
Strategy: validation
Validate before calling
if t := registry[name]; t != nil && t.Handler == nil && t.ContextHandler == nil {
return fmt.Errorf("tool %q registered without handler", name)
} Try / catch
if _, _, err := validateToolCallInput(ctx, name, args); err != nil {
if strings.HasPrefix(err.Error(), "tool handler unavailable:") { /* fail registration-time, not call-time */ }
} Prevention
- Assign Handler at the same call site where the Tool literal is declared
- Add a startup invariant test asserting every registered tool has a handler
- Filter platform-gated tools out of the registry when their handler file is excluded
When it happens
Trigger: Calling validateToolCallInput with a toolName whose registered tools.Tool struct has ContextHandler == nil && Handler == nil — typically a mis-initialized Tool literal or a tool whose handler was stripped during refactoring.
Common situations: A developer adds a tool entry with only metadata (name/description/schema) and forgets to assign Handler; a build-tag or platform-gated file that sets the handler is excluded so the field stays nil; tests construct Tool structs without handlers to exercise this branch.
Related errors
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/047bb8bf95e7395e.
Report an issue: GitHub.