siyuan-note/siyuan · warning
tool execution was cancelled before it started
Error message
tool execution was cancelled before it started
What it means
validateToolCallInput checks ctx.Err() before validating input or executing the tool. If the context is already cancelled or its deadline exceeded, the tool call is rejected with this static message so work never starts on a dead context.
Solutions
- Use a context not tied to an already-finished request for background tool execution
- Increase the timeout budget if legitimate calls are being cancelled prematurely
- Check ctx.Err() before dispatching each tool call in batch flows to skip work early
- Propagate cancellation intent properly so callers can distinguish user cancel from timeout
Example fix
// before: reusing a finished request context for background work validateToolCallInput(req.Context(), toolName, args) // after: use a detached context with its own deadline ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() validateToolCallInput(ctx, toolName, args)
Defensive patterns
Strategy: type-guard
Validate before calling
if ctx.Err() != nil {
return fmt.Errorf("skip tool call: context already done (%v)", ctx.Err())
} Type guard
func ctxAlive(ctx context.Context) bool {
select {
case <-ctx.Done():
return false
default:
return true
}
} Try / catch
if err != nil && strings.Contains(err.Error(), "cancelled before it started") {
// benign: the caller's context ended; do not retry with the same context
return context.Canceled
} Prevention
- Never reuse request-scoped contexts for background work
- Set realistic timeouts on long tool flows
- Check context liveness before dispatching each tool call
When it happens
Trigger: Any tool call issued with a context that has already been cancelled: the HTTP request was aborted, the client disconnected, the parent turn was cancelled, or the deadline expired before the call ran.
Common situations: Users canceling a chat turn while the model streams tool calls; long-queued requests whose timeout elapsed; background jobs reusing a request-scoped context that ended.
Related errors
- capability execution was cancelled before it started
- image generation was cancelled
- invalid tool arguments
- tool handler unavailable
- unknown tool
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/2b642bb4de211834.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/agent/tools.go:48
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 {
return fmt.Errorf("capability execution was cancelled before it started")
}
if registration.Validator == nil {View on GitHub (pinned to 9f775e8a12)