projectdiscovery/katana · error
captcha solve timed out after
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.
Solutions
- Log the captcha provider and taskID and retry the Solve call once; many solves succeed on a second attempt
- 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)
- Check the CapSolver dashboard/account for queue issues, balance, or rate limits
- Try a different solver provider registered in the solverRegistry (e.g. 2captcha)
- 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
- Give Solve a parent context with >= 120s of budget
- Check CapSolver account balance/queue health before large crawls
- Cap concurrency so the CapSolver queue isn't saturated
- Retry once on timeout with a fresh context
- Consider a fallback solver provider
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
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- create task
- capsolver error
- captcha solve
- no token found in capsolver solution
- unsupported captcha provider for capsolver
AI-assisted analysis of projectdiscovery/katana@e3e742739c (2026-09-03).
Data as JSON: /api/errors/b919e170985da980.
Report an issue: GitHub.
Appendix: 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)