
Lucas Mitchell
Automation Engineer

A worker stops after submitting a task, and another worker starts with only the original instruction. Without a checkpoint, the replacement may submit again, reset its retry counter or use a result in a different browser session. The failure is no longer just an unavailable worker: the system has lost its account of work already attempted.
Node.js long-running agent CAPTCHA handling needs a state contract that survives that interruption. CapSolver can supply the documented solving step, while the application records what it requested and whether the browser accepted the result. This tutorial builds the routing part of that contract with a small local example. It makes the ambiguous cases visible instead of treating every restart as a new task, and it keeps provider integration separate from the checkpoint demonstration.
A checkpoint should preserve the stage, task reference, attempt budget and original deadline needed to decide the next permitted operation.
Use an application job ID and a checkpoint version to correlate events. Record a reference to the owning session, an authority decision that can be rechecked, and the last confirmed stage. A provider task reference belongs with the job that created it. A challenge token, account password or full browser profile should not be copied into a general workflow log.
The stages used below are application labels, not CapSolver API statuses. observed means the application has identified a supported checkpoint; submission_unknown means a submission may have reached the service; submitted means a task reference is known; result_ready means the next action is page verification; and complete records a finished job.
A serialized boolean saying an operation was allowed yesterday is not sufficient authority for a later action. A production worker must refresh permission and session observations before routing work. The fixture receives those observations as inputs so the routing decision is testable without credentials.
Similarly, a session reference is not proof that the browser still exists. The worker needs a real session manager to validate ownership and readiness. If that validation fails, inspect or recreate the authorized workflow according to application policy before attempting further work.
Submission starts work, while polling asks about a task the service has already identified.
The CapSolver task creation reference documents responses that return results immediately and responses that require later retrieval. For an asynchronous response, retain the task identifier and use the result retrieval contract. As checked on September 8, 2026, that contract specifies a five-minute retrieval window and a maximum of 120 query requests per task. Those are provider limits, not recommended application budgets.
A job deadline may be shorter, and the browser's useful state may expire earlier. Keep those boundaries separate. Polling an existing task also needs its own count and timing policy; the fixture's attempt count represents new submissions only.
If the response disappears after a submission, the caller may not know whether the service accepted it. The HTTP semantics standard explains why automatic retries require care for operations that are not known to be idempotent. An application job ID does not create remote deduplication unless the service explicitly supports that contract.
Route the uncertainty to reconciliation using available provider evidence or an operator. If the outcome cannot be established, preserve that unresolved state. Do not fabricate a provider idempotency parameter or describe a local retry counter as an exactly-once guarantee.
Redeem Your CapSolver Bonus Code
Boost your automation budget instantly!
Use bonus code CAP26 when topping up your CapSolver account to get an extra 5% bonus on every recharge — with no limits.
Redeem it now in your CapSolver Dashboard
You can test restart decisions with a pure routing function and a local checkpoint round-trip before attaching any external action.
Save the following as checkpoint.mjs and run node checkpoint.mjs. It uses Node.js built-ins and was executed with Node.js 24.19.0. The example uses synthetic time values and task references, performs no network calls and deletes only the temporary directory it creates. It demonstrates state serialization, not crash-safe distributed persistence.
import assert from 'node:assert/strict';
import { mkdtemp, writeFile, readFile, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
function nextAction(s, now) {
if (!Number.isFinite(now) || !Number.isFinite(s.deadline) ||
!Number.isInteger(s.attempts) || s.attempts < 0 ||
!Number.isInteger(s.limit) || s.limit < 1) throw Error('invalid budget');
if (!['observed','submission_unknown','submitted','result_ready','complete'].includes(s.stage))
throw Error('invalid stage');
if (s.stage === 'complete') return 'done';
if (s.allowed !== true) return 'stop_authority';
if (now >= s.deadline) return 'stop_deadline';
if (s.sessionCurrent !== true) return 'inspect_session';
if (s.stage === 'submission_unknown') return 'reconcile_submission';
if (s.stage === 'submitted') return s.taskRef ? 'poll_existing' : 'reconcile_submission';
if (s.stage === 'result_ready') return 'verify_page';
return s.attempts < s.limit ? 'submit_once' : 'stop_budget';
}
const initial = {stage:'observed', allowed:true, sessionCurrent:true,
attempts:0, limit:2, deadline:100, taskRef:null};
const cases = [
[initial, 1, 'submit_once'],
[{...initial, stage:'submission_unknown', attempts:1}, 2, 'reconcile_submission'],
[{...initial, stage:'submitted', attempts:2, taskRef:'demo-task'}, 3, 'poll_existing'],
[{...initial, stage:'result_ready'}, 4, 'verify_page'],
[{...initial, attempts:2}, 5, 'stop_budget'],
[initial, 100, 'stop_deadline'],
[{...initial, allowed:false}, 1, 'stop_authority'],
[{...initial, sessionCurrent:false}, 1, 'inspect_session'],
[{...initial, stage:'complete', allowed:false}, 200, 'done']
];
for (const [state, now, expected] of cases) assert.equal(nextAction(state, now), expected);
assert.throws(() => nextAction({...initial, attempts:-1}, 0));
assert.throws(() => nextAction({...initial, stage:'typo'}, 0));
// Synthetic checkpoint round-trip: no browser, provider or business write.
const dir = await mkdtemp(join(tmpdir(), 'agent-state-'));
try {
const file = join(dir, 'checkpoint.json');
const state = {...initial, stage:'submitted', attempts:1, taskRef:'demo-task'};
await writeFile(file, JSON.stringify(state), {mode:0o600});
const restored = JSON.parse(await readFile(file, 'utf8'));
assert.deepEqual(restored, state);
assert.equal(nextAction(restored, 2), 'poll_existing');
assert.equal(nextAction(restored, 100), 'stop_deadline');
console.log(JSON.stringify({routes:cases.length, invalidStatesRejected:2,
restoredAction:nextAction(restored,2), expiredAction:nextAction(restored,100)}));
} finally { await rm(dir, {recursive:true, force:true}); }
The run reports nine routing cases, two rejected malformed states and a restored decision of poll_existing. At the original deadline, the same restored job returns stop_deadline. The elapsed budget survives because the saved deadline is reused instead of being recalculated from the restart time.
The Node.js file-system API supports the write/read sequence shown. A successful round-trip proves that the serialized fields can be restored for this test. It does not prove that a write interrupted by power loss is durable, that two workers cannot claim the same job or that a remote task and local checkpoint commit together.
Use a production store with version checks and exclusive job claims. Persist the uncertain-submission stage before dispatch, and record the returned task reference when available. Test worker termination between each boundary. A file write without those controls should not be presented as a distributed queue.
The worker should enforce the saved job deadline before starting an operation and pass an appropriate cancellation signal to each supported local wait or request.
Node.js documents AbortSignal.timeout in its global API reference. That can bound a supported client operation, but a cancelled wait does not prove remote task cancellation. The routing fixture only decides whether an operation is eligible to start; it does not implement transport cancellation or poll scheduling.
Use a separate retry policy for each failure class. Configuration errors, unavailable browser state and access decisions require different actions. The CapSolver error reference should inform the provider adapter, while the MCP troubleshooting article provides related integration context. Return concise error classes to the agent and keep credentials out of the visible result.
The agent may continue only when the current authority, session and application acceptance checks allow the next action.
A result_ready checkpoint routes to verify_page; it does not route directly to a business write. The page adapter must confirm the relevant postcondition in the intended session. If an application write has already been attempted but its response is lost, reconcile that write independently of the CAPTCHA task. Solving again cannot establish whether the original write happened.
The AI and automation FAQ helps define product scope. Keep the runtime accountable for session ownership, storage, deadlines and final action state when using CapSolver. Once those responsibilities are explicit, a restart can resume a known workflow state instead of repeating an instruction with missing history.
Q: Should a restart reset the CAPTCHA attempt counter?
No. Preserve the counter and original deadline for the same job. A new attempt budget should require an explicit application decision, not merely a new process.
Q: Can a known provider task be reused after any length of time?
No. Follow the provider's retrieval window and request limits, and also check whether the browser session and original operation are still valid.
Q: Does the example guarantee exactly-once task creation?
No. It tests routing and a local file round-trip. Distributed claims, crash durability and remote submission reconciliation require additional infrastructure and tests.
Q: Is a provider result enough to mark the job complete?
No. Verify the intended application outcome. If the page did not advance or a business action is uncertain, preserve that separate state instead of declaring completion.
Evaluate enterprise CAPTCHA services with a focused pilot covering task compatibility, accepted outcomes, cost attribution, security evidence, and support.

Design AI agent web scraping with separate access and extraction layers, runnable Python, bounded retries, retained snapshots, and structured data checks.
