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

  1. Assign a valid Handler (or ContextHandler) to the tool's tools.Tool definition in the registry.
  2. 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.
  3. 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

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.