Repository navigation
fix(crawl): exit 1 when a crawl stops because credits ran out - #49
Merged
Merged
Conversation
`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.
The lock lives beside the contract, not in a surface. main already dropped it, and this branch had brought it back.
StefanoGuerrini
approved these changes
Oct 16, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Before
crawlFailurereturned an error only for afailedcrawl, andprintCrawlprinted everystoppedcrawl as a warning and exited 0.crawl stopprinted "Stopped crawl" for anystoppedanswer. The crawl skill said "Onlyfailedexits non-zero". Onlyinsufficient_creditshad next-step advice.After
A
stoppedcrawl that carries anerror(the account ran out of credits, or the API key hit a credit cap, while it ran) counts as a failure.create --follow,getandwaitexit 1 withok: falseand the code a create refused with 402 already gets:CRAWL_INSUFFICIENT_CREDITSforerror.codeinsufficient_credits,CRAWL_KEY_CAP_REACHEDforapi_key_cap_reached. The error carries the same next-step advice as those codes, the API's code asserver_code, itserror.detailinlikely_cause, andcrawl_id. Afailedcrawl 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 givesCRAWL_FAILEDwithserver_codeand the message "Crawl stopped before it finished."crawl stopon such a crawl prints "Nothing to stop: crawl had already ended". A crawl stopped withstop_reasonuserstill exits 0.quotaExhaustedtakesstatus: null, askeyCapReachedalready does, so itslikely_causenames the crawl instead of an HTTP status.skills/crawl/SKILL.md,skills/trace-debug/SKILL.mdand the README describe the two meanings ofstoppedand the exit code.Checks
npm run typecheckandnpm testpass (366 tests). A test runscrawl geton a crawl that endedstoppedandfailedwith 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 givesCRAWL_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 endfailed, which this CLI reports with the same credit codes.