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
- 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: %w
- unsupported captcha provider for capsolver: %s
- capsolver error %s: %s
- no token found in capsolver solution (key=%s)
- captcha solve: %w
AI-assisted analysis of projectdiscovery/katana@e3e742739c (2026-09-03).
Data as JSON: /api/errors/b919e170985da980.
Report an issue: GitHub.