projectdiscovery/katana · error

captcha solve timed out after %s

Error message

captcha solve timed out after %s

What it means

pollResult polls the CapSolver /getTaskResult endpoint every 3s, wrapped in a 120s context deadline. If the context deadline or a parent cancellation fires before CapSolver reports status="ready", the loop exits via the ctx.Done() case and returns this error. It means the captcha was never solved within the 120s budget, not that solving failed outright.

Source

Thrown at pkg/engine/headless/captcha/capsolver/capsolver.go:147

		return "", err
	}
	if result.ErrorID != 0 {
		return "", fmt.Errorf("capsolver error %s: %s", result.ErrorCode, result.ErrorDescription)
	}
	return result.TaskID, nil
}

func (s *Solver) pollResult(ctx context.Context, taskID string, provider captcha.Provider) (*captcha.Solution, error) {
	ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
	defer cancel()

	ticker := time.NewTicker(defaultPollInterval)
	defer ticker.Stop()

	for {
		select {
		case <-ctx.Done():
			return nil, fmt.Errorf("captcha solve timed out after %s", defaultTimeout)
		case <-ticker.C:
			result, err := s.getTaskResult(ctx, taskID)
			if err != nil {
				return nil, err
			}
			if result.ErrorID != 0 {
				return nil, fmt.Errorf("capsolver error %s: %s", result.ErrorCode, result.ErrorDescription)
			}
			if result.Status != "ready" {
				continue
			}
			return extractToken(result.Solution, provider)
		}
	}
}

func (s *Solver) getTaskResult(ctx context.Context, taskID string) (*getTaskResultResponse, error) {
	body, err := json.Marshal(getTaskResultRequest{

View on GitHub (pinned to e3e742739c)

Solutions

  1. Log the captcha provider and taskID and retry the Solve call once; many solves succeed on a second attempt
  2. Increase the budget by passing a parent context with a longer or no extra deadline (the 120s defaultTimeout is hardcoded, so parent ctx must be >= 120s)
  3. Check the CapSolver dashboard/account for queue issues, balance, or rate limits
  4. Try a different solver provider registered in the solverRegistry (e.g. 2captcha)
  5. Do not retry with the already-cancelled context; create a fresh context per attempt

Example fix

// before
sol, err := handler.Solve(ctx, info) // ctx already nearly expired

// after
solveCtx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
defer cancel()
sol, err := handler.Solve(solveCtx, info)
Defensive patterns

Strategy: retry

Validate before calling

if ctx.Err() != nil || deadlineTooShort(ctx, 120*time.Second) {
	// extend: solveCtx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
}

Try / catch

for attempt := 0; attempt < 2; attempt++ {
	solveCtx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
	sol, err := solver.Solve(solveCtx, info)
	cancel()
	if err == nil || !strings.Contains(err.Error(), "timed out") {
		break
	}
}

Prevention

When it happens

Trigger: CapSolver queue is slow (hard captcha type like hCaptcha or ReCaptchaV2Enterprise), the API is degraded, the task was created but never completes, or the caller's parent context is cancelled/times out first and the wrapped ctx.Done() fires.

Common situations: Bulk crawling with many concurrent captcha solves hitting CapSolver rate/queue limits; free-tier API key with low priority; target site serving challenges with very low solve success; network latency causing createTask to consume much of the budget.

Understand the failure class

Related errors


AI-assisted analysis of projectdiscovery/katana@e3e742739c (2026-09-03). Data as JSON: /api/errors/b919e170985da980. Report an issue: GitHub.