Failure codes

A run that does not reach its goal carries a failure code: one word from a closed set that says which KIND of thing went wrong, so you can branch on it in code instead of matching a sentence. Every code is below, with the evidence behind it and what to do about it. The list is generated from the same registry the engine classifies into — nothing here is a second copy.

Where you meet a code

On the run detail page, under the diagnosis, next to a link back to this page. In the API, a POST /api/v1/run response for a run that missed its goal carries failureCode (always one of the codes below) and failureCodeUrl (a link straight to its row here). A successful run carries neither, which is the same thing success: true already tells you.

The set is closed and this page is the contract. New codes get added here first; existing ones are not renamed or repurposed.

When there is no code

Two different situations look alike and are not. unrecorded means we looked when the run settled and nothing had been written down — no engine reason, no block, no warning. It is a real code, kept deliberately, because that is a gap in what we record and it is worth counting. An absent code means something else entirely: the run succeeded, is still going, or finished before failure codes existed. We do not guess a reason for either, and the run page says which one it is looking at.

The stored identity

Four different things go wrong with a login and they have four different fixes — which is the entire reason they are four codes. Three of them are deterministic: re-running types the same missing, wrong or withheld credential again.

CodeWhat it meansWhat to do
credential_missingRetrying will not helpChargedIt needed a credential that is not stored. The sign-in asked for a secret the vault does not hold, so the run could not get past it. The engine stopped at the form rather than guessing a value.How we know · The engine returns a top-level `code: "credential_missing"` naming the secret the vault could not supply.Store the login. Add it under Logins & secrets, or send the user a connect link and let them sign in once.
credential_rejectedRetrying will not helpChargedThe site refused the credential it typed. The value IS stored. It was typed into the site’s own form, submitted, and the site answered that it is wrong. Running again types the same wrong string.How we know · The engine returns `code: "credential_rejected"` with the vault KEY NAMES it filled and the refusal phrase. It is raised only after a real submit — refusal wording sits on login pages before any attempt.Replace the stored value. This is the one login failure whose fix is editing the secret, not re-running.
credential_blockedRetrying will not helpChargedIt held the credential back. The secret is stored and was deliberately NOT typed: the run reached a host this site was never observed to hand its sign-in to. Storing it again changes nothing — it is already stored.How we know · The engine returns `code: "credential_blocked"` with the host it declined to type on. The opposite of credential_missing, which is why they are not one code.Review the warning. The warning names both hosts. Authorise the handoff, or capture a session by hand instead.
session_staleRetrying can helpChargedThe saved session had gone stale. A stored browser session was restored for this host and the site no longer honours it, so the run met a sign-in wall it was supposed to be past.How we know · A stored jar was loaded for this run AND the run missed its goal, with no credential code and no wall recorded. The weakest inference in the set, which is why every other code outranks it.Capture a fresh session. Running again attempts a normal sign-in, which overwrites the dead jar when it works.

The target pushed back

The engine fingerprints the anti-bot vendor it met and reports it structurally. The distinction that matters is not which product it is but whether a browser can pass it at all.

CodeWhat it meansWhat to do
score_wallRetrying will not helpChargedA bot wall no automated browser passes. A reputation-scored defence (reCAPTCHA Enterprise, DataDome, PerimeterX, Kasada, Akamai, Imperva). The verdict is not decided by the browser, so no fleet or proxy tier reliably clears it.How we know · The engine fingerprinted a vendor in the SCORE set (`lib/evasion-policy.ts`). Not a widget — there is nothing on the page to solve.Capture the session by hand. A person signs in once; every run after reuses that session and never meets the wall again.
bot_wallRetry on stealthChargedThe site refused the run at a bot check. An interactive challenge or interstitial — the kind a solver or the stealth fleet can genuinely clear, unlike a score wall.How we know · The engine fingerprinted a vendor in the CHALLENGE set, or reported `outcome: "challenged"` / `"blocked"`. The vendor set is the whole difference from score_wall.Retry on the stealth fleet. Stealth is the premium tier; a Pro workspace routes a host with a recorded wall to it automatically.
rate_limitedRetrying will not helpChargedThe site throttled this identity. Not a wall and not a logic failure — too many requests from this exit or this account, too fast. The site would have served the same request later.How we know · The engine reported `blocked.outcome === "rate-limited"` — its own field value, not a reading of the page.Wait before retrying. A different account, or a different proxy region (`country`), also resets it.
connect_requiredRetrying will not helpNever startedRefused before it started — this host needs a human sign-in. This run never opened a browser. The host has a recorded history of score-walling runs, so it was refused up front instead of buying the same wall again.How we know · Written on the fastpath branch, where no engine call is made at all. score_wall means THIS run met the wall; connect_required means we declined to let it.Send a connect link. Your user signs in once in their own browser; the captured session carries every run after it.

It never got there

Nothing about the target site. These are our side of the wire — the exit, the container, the stream — and all three release the reservation.

CodeWhat it meansWhat to do
egress_deadRetrying can helpRefunded in fullNo exit could reach the sign-in host. Every proxy exit in the chain was refused a connection to the host this site signs in at — a provider policy or a dead tunnel, not a bot wall. The browser was rendering a Chrome error page, not the site.How we know · The engine reported `egressFail` with a Chromium `ERR_*` code (`src/exec/net-error.js`) and the sign-in host it was refused on. A code the prose column could never carry.Try another region. Set a different `country`, or start the run at the sign-in host directly. It is recorded per host, so the next run is routed around it.
engine_unreachableRetrying can helpRefunded in fullThe engine could not be reached. Our infrastructure, not the target site. The browser fleet did not answer, or answered with an error.How we know · The request to the fleet threw or returned a non-2xx before any verdict existed. There is no engine answer to classify.Retry. Nothing on your side caused this and nothing on your side fixes it. The reservation was released in full.
stream_lostRetrying can helpRefunded in fullThe run went silent and was cut loose. The live stream stopped delivering — a container replaced mid-run, an eviction, a crash. A watchdog ended the run rather than leaving it running forever. What the agent had done up to that point is recorded; why it stopped is not, because nothing reported it.How we know · The stream ended without a terminal result frame. Distinct from engine_unreachable: the engine answered and then stopped.Retry. The reservation was released in full, so the retry starts from the same place the first attempt did.

The request was refused

Decided before any browser opened, so nothing was spent on them.

CodeWhat it meansWhat to do
no_creditsRetrying can helpNever startedIt never started — the credit hold failed. The run was created and then refused before any browser opened, because the workspace could not cover the reservation.How we know · The reservation call failed. No engine call was made, so there is no verdict to classify.Top up. Add credits, or turn on auto top-up so this cannot stop a scheduled run.
target_unusableRetrying will not helpNever startedThere was no site to start on. The request had no usable start URL — an attachment alone, or an instruction that named no site. It is refused with a 400 and no run is created.How we know · Target resolution returned no usable URL. Decided before the run row exists, so this appears in the API response rather than on a run.Name the site. Include the site, or paste its URL, in the instruction.

The agent stopped

The engine reached the site, worked, and ended without meeting the success condition. These are the codes where the run is charged in full.

CodeWhat it meansWhat to do
needs_humanRetrying will not helpChargedIt hit a verification only a person can clear. A 2FA prompt, an approval, an identity check. Normally the run PARKS here and waits for a code; it only fails this way when human-in-the-loop was turned off (`hitl: false`) or nothing resumed it.How we know · The engine set its terminal reason to `needs human: …` — written verbatim by the agent loop when it yields a needs-human action.Resume it, or leave HITL on. Supply the code with POST /runs/{id}/resume, drive the browser through the live view, or hand the sign-in to your end-user with a connect link.
gave_upRetrying can helpChargedThe agent stopped before reaching the goal. The engine’s own terminal reason, verbatim: a step budget spent, a page that stopped advancing, no element that moved the goal forward, or a success condition that was never satisfied. The catch-all for a run that worked and did not get there.How we know · The engine answered with success:false and a terminal reason that matches none of the structured signals above. A run that exceeded the engine’s own time budget also arrives here: it is not reported as a distinct reason today.Sharpen the instruction. Name the element or the page you expect, or tighten the success condition so a partial result is not read as a miss.

Nothing was recorded

Kept as a real code rather than a blank, because "we do not know" is an answer and a silently empty field is not.

CodeWhat it meansWhat to do
unrecordedRetrying can helpChargedNo reason was recorded. The run ended without reaching its goal and nothing was written to explain why — no engine reason, no block, no warning. That is a gap in what we record, not a finding about the site. We are not going to guess one.How we know · Stored deliberately. An EMPTY failure_code means the run predates the column; `unrecorded` means we looked at settle time and there was nothing there. Those are different facts.Look at the steps. The recorded actions and the phase the run stopped in are everything there is for this run.

Terms used above — connect link, session, stealth fleet, HITL — are defined in the Lexicon. The request and response shapes that carry these codes are in the API reference.