Handle Go SDK errors and recover interrupted tasks
A timeout doesn't mean task creation failed. Keep the original task inputs and the SDK's request ID. If you have a task ID, resume with GetTask; otherwise, replay CreateTask with the exact original inputs and the same request ID.
Cancellation stops the Go client from waiting, not the remote task. Recover that task before paying for a new solve.
On this page
Run the error-handling example
Use Go 1.22 or later with the official solvethree.com/sdk module, version v0.1.0. See the Go integration instructions for setup.
Set the server-side environment variables SOLVETHREE_API_KEY and SOLVETHREE_PROXY. Replace the example's Site and Action with your target hostname and action. The site needs an active profile in your workspace; sending its hostname doesn't create one. This reCAPTCHA request uses a single proxy and doesn't need Type or URL.
package main
import (
"context"
"fmt"
"errors"
"log"
"os"
"time"
solvethree "solvethree.com/sdk"
)
func main() {
client, err := solvethree.NewClient(os.Getenv("SOLVETHREE_API_KEY"), nil)
if err != nil { log.Fatal(err) }
ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
defer cancel()
input := solvethree.TaskRequest{Site: "accounts.example.com", Action: "login", Proxy: os.Getenv("SOLVETHREE_PROXY")}
result, err := client.Solve(ctx, input, solvethree.SolveOptions{})
if err != nil {
var failed *solvethree.TaskFailedError
var api *solvethree.APIError
var recovery *solvethree.Error
switch {
case errors.As(err, &failed):
log.Printf("Failed task %s: %s", failed.TaskID, failed.ErrorCode)
case errors.As(err, &recovery):
log.Printf("Recovery request=%s task=%s", recovery.RequestID, recovery.TaskID)
if errors.As(err, &api) { log.Printf("HTTP %d; retry delay %s", api.StatusCode, api.RetryAfter) }
// Save input plus recovery.RequestID. Resume GetTask for a known TaskID;
// otherwise replay CreateTask with identical inputs and that RequestID.
}
return
}
fmt.Println("Ready task:", result.ID)
// Submit result.Solution.Token with its browser identity and original proxy.
}The example sets a 180-second context deadline and logs recovery IDs when an SDK error exposes them. Logging isn't durable recovery: save the original input, the returned RequestID, and any TaskID before discarding the error.
“Ready task” means a solution is available, not that the target accepted it. The SDK doesn't submit it for you. Submit Solution.Token using the returned browser identity and, for this single-proxy example, the original proxy.
Distinguish a failed task from an interrupted request
Inspect wrapped errors with errors.As and cancellation with errors.Is. These checks answer different questions: one error can contain recovery IDs, wrap an HTTP error, and still need to be handled as an interrupted operation.
| Check | Meaning and next action |
|---|---|
errors.As for *solvethree.TaskFailedError | The task reached terminal failure. Read TaskID, RequestID, and ErrorCode. Retrieve the failed task with GetTask for its Error text, then stop polling that task. Don't blindly resubmit unchanged inputs. |
errors.Is for context.DeadlineExceeded or context.Canceled | The caller stopped waiting. This doesn't establish whether creation succeeded or the remote task finished. Retain the recovery information and resume with a fresh context when appropriate. |
errors.As for *solvethree.Error | The SDK wrapper exposes RequestID and TaskID and wraps its underlying Cause. Save the IDs alongside the exact original inputs. |
errors.As for *solvethree.APIError | Inspect the HTTP StatusCode and RetryAfter duration. An HTTP error is different from a task whose status is failed; don't assume you received a successful task payload. |
The example checks for a nested APIError inside its recovery branch. Check cancellation separately with errors.Is if your application needs to distinguish a deadline from an explicit cancellation.
Solve creates the task once. It retries only temporary polling responses with HTTP 429, 502, 503, or 504, honoring Retry-After within the context deadline. CreateTask never automatically retries its POST.
Resume the existing task
- 1.
Keep the original request intact
Save all task inputs, including the proxy or candidate list, together with the request ID. CreateTask generates a request ID when you omit it; capture the generated value exposed by the recovery error. Don't reconstruct the request later from configuration that may have changed.
- 2.
Use GetTask when you know the task ID
Create a fresh context if the old one expired or was canceled, then retrieve the saved TaskID with GetTask. Continue waiting for pending or solving, consume the solution only for ready, and stop on failed.
- 3.
Replay creation only when you don't know the task ID
Set RequestID on the saved original TaskRequest to the recovered request ID, then call CreateTask with those same inputs. Reusing the same ID and request returns the existing task. Changing the inputs while reusing that ID causes a conflict.
Don't use a new request ID merely because the connection timed out. That starts a new logical task rather than recovering the uncertain one. First establish what happened to the original task; if it failed, inspect its error before deciding whether a corrected request is needed.
Sources and API reference
Run your first task.
Start with $5 in workspace credit.