Skip to content

fix(crawl): exit 1 when a crawl stops because credits ran out - #49

Merged
StefanoGuerrini merged 8 commits into
mainfrom
fix/crawl-stopped-on-credits
Oct 16, 2026
Merged

StefanoGuerrini merged 8 commits into
mainfrom
fix/crawl-stopped-on-credits

Conversation

@zenrows-dev-pool

@zenrows-dev-pool zenrows-dev-pool Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Before

crawlFailure returned an error only for a failed crawl, and printCrawl printed every stopped crawl as a warning and exited 0. crawl stop printed "Stopped crawl" for any stopped answer. The crawl skill said "Only failed exits non-zero". Only insufficient_credits had next-step advice.

After

A stopped crawl that carries an error (the account ran out of credits, or the API key hit a credit cap, while it ran) counts as a failure. create --follow, get and wait exit 1 with ok: false and the code a create refused with 402 already gets: CRAWL_INSUFFICIENT_CREDITS for error.code insufficient_credits, CRAWL_KEY_CAP_REACHED for api_key_cap_reached. The error carries the same next-step advice as those codes, the API's code as server_code, its error.detail in likely_cause, and crawl_id. A failed crawl with either code, as older deployments report it, maps the same way, so callers see one code whatever the server version. Any other error on a stopped crawl gives CRAWL_FAILED with server_code and the message "Crawl stopped before it finished." crawl stop on such a crawl prints "Nothing to stop: crawl had already ended". A crawl stopped with stop_reason user still exits 0.

quotaExhausted takes status: null, as keyCapReached already does, so its likely_cause names the crawl instead of an HTTP status.

skills/crawl/SKILL.md, skills/trace-debug/SKILL.md and the README describe the two meanings of stopped and the exit code.

Checks

npm run typecheck and npm test pass (366 tests). A test runs crawl get on a crawl that ended stopped and failed with each credit code and checks exit 1, ok: false, the toolkit code, server_code, crawl_id, the shared advice and that the results are still printed. Another checks that a crawl stopped with any other code gives CRAWL_FAILED.

Merge order

This builds on #44. Merge it after #44, and only once the Crawl API ends crawls that run out of credits as stopped. Until then those crawls still end failed, which this CLI reports with the same credit codes.

`crawl create --output-format markdown` stores each kept URL's page
converted to Markdown, read back with `crawl content` and `crawl
download` as `--output-format html` already was. A value that is neither
is still refused locally, before any network call.

A crawl also reports `discovery`, the strategies it found URLs with, so
`crawl get --json` carries it instead of dropping it.
The lock names the API description this surface was synced to, by its
blob id, so a later drift check compares against it instead of reporting
the surface as never synced. Two operations in that description are not
served to callers and no surface exposes them; the lock lists them as
pending, so a change to either does not read as drift here.
The Crawl API now ends a crawl that runs out of credits, or whose API key
hits a credit cap, as `stopped` with `error.code` `insufficient_credits` or
`api_key_cap_reached` and no `stop_reason`, instead of `failed`. The CLI
treated every `stopped` crawl as a deliberate stop, so such a crawl would
have exited 0 with `ok: true`. It now exits 1 with `CRAWL_FAILED` and the
API's code as `server_code`, as it did when the crawl ended `failed`, with
next-step advice for both codes. `crawl stop` on such a crawl says it had
already ended. The crawl skill, the trace-debug skill and the README say so.
A crawl that ends `stopped` because the account ran out of credits or the
API key hit a credit cap now exits 1 with `CRAWL_INSUFFICIENT_CREDITS` or
`CRAWL_KEY_CAP_REACHED`, the codes a create refused with 402 already gets,
instead of `CRAWL_FAILED`. It carries the same next-step advice as those
codes, the API's `error.code` as `server_code`, its `error.detail` in
`likely_cause`, and `crawl_id`. A `failed` crawl with either of those
codes, as older deployments report it, maps the same way, so callers see
one code whatever the server version. Any other error on a stopped or
failed crawl stays `CRAWL_FAILED`.

`quotaExhausted` takes `status: null`, as `keyCapReached` does, for an
error read from a crawl rather than an HTTP 402. The crawl skill, the
trace-debug skill and the README say so.
The fixtures now carry the error.detail the server returns when credits end a crawl, one per error code.
@zenrows-dev-pool
zenrows-dev-pool Bot marked this pull request as ready for review October 15, 2026 16:16
Base automatically changed from feat/crawl-markdown to main October 16, 2026 07:06
zenrows-dev-pool Bot and others added 3 commits October 16, 2026 08:11
The lock lives beside the contract, not in a surface. main already
dropped it, and this branch had brought it back.
@StefanoGuerrini
StefanoGuerrini merged commit 8661688 into main Oct 16, 2026
3 checks passed
@StefanoGuerrini
StefanoGuerrini deleted the fix/crawl-stopped-on-credits branch October 16, 2026 11:22
@github-actions github-actions Bot mentioned this pull request Oct 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant