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

  1. Use a context not tied to an already-finished request for background tool execution
  2. Increase the timeout budget if legitimate calls are being cancelled prematurely
  3. Check ctx.Err() before dispatching each tool call in batch flows to skip work early
  4. 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

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


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)