{"record":{"id":"047bb8bf95e7395e","repo":"siyuan-note/siyuan","slug":"tool-handler-unavailable-s","errorCode":null,"errorMessage":"tool handler unavailable: %s","messagePattern":"tool handler unavailable: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/agent/tools.go","lineNumber":45,"sourceCode":"\t\"github.com/siyuan-note/siyuan/kernel/mcp/tools\"\n\tkernelModel \"github.com/siyuan-note/siyuan/kernel/model\"\n)\n\ntype executedToolResult struct {\n\tText             string\n\tModelAttachments []tools.ModelAttachment\n\tIsError          bool\n\tExecutionUnknown bool\n}\n\n// validateToolCallInput 在确认和快照之前校验工具调用，避免无效调用被误判为写操作。\nfunc validateToolCallInput(ctx context.Context, toolName string, args map[string]any) (*tools.Tool, *tools.ToolValidator, error) {\n\tt, validator := tools.LookupToolWithValidator(toolName)\n\tif t == nil {\n\t\treturn nil, nil, fmt.Errorf(\"unknown tool: %s\", toolName)\n\t}\n\tif t.ContextHandler == nil && t.Handler == nil {\n\t\treturn nil, nil, fmt.Errorf(\"tool handler unavailable: %s\", toolName)\n\t}\n\tif ctx.Err() != nil {\n\t\treturn nil, nil, fmt.Errorf(\"tool execution was cancelled before it started\")\n\t}\n\tif err := validator.ValidateInputContext(ctx, args); err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"invalid tool arguments: %w\", err)\n\t}\n\treturn t, validator, nil\n}\n\nfunc validateCapabilityCall(ctx context.Context, registration *capabilityRegistration, args map[string]any) error {\n\tif registration == nil {\n\t\treturn fmt.Errorf(\"capability was not exposed in this model round\")\n\t}\n\tif !capabilityStillExecutable(registration, args) {\n\t\treturn fmt.Errorf(\"capability is disabled or no longer available: %s\", registration.ID)\n\t}\n\tif ctx.Err() != nil {","sourceCodeStart":27,"sourceCodeEnd":63,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/agent/tools.go#L27-L63","documentation":"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.","triggerScenarios":"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.","commonSituations":"Feature flags disabling a capability while leaving its tool registered; partially initialized tool registrations during startup; copy-pasted tool definitions missing the handler field.","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"],"exampleFix":"// before: registered without a handler\ntools.Register(&tools.Tool{Name: \"export\", Schema: schema})\n// after\ntools.Register(&tools.Tool{Name: \"export\", Schema: schema, Handler: exportHandler})","handlingStrategy":"validation","validationCode":"for _, t := range registeredTools {\n    if t.Handler == nil && t.ContextHandler == nil {\n        return fmt.Errorf(\"tool %q registered without a handler\", t.Name)\n    }\n}","typeGuard":"func hasHandler(t *tools.Tool) bool {\n    return t != nil && (t.Handler != nil || t.ContextHandler != nil)\n}","tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"tool handler unavailable\") {\n    return fmt.Errorf(\"tool %q is registered but has no executable handler; fix registration\", toolName)\n}","preventionTips":["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"],"tags":["agent","tools","registration"],"backgroundTag":"internal-invariant-violation","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}