Skip to content

feat(crawl): read the discovery strategies a crawl ran with, and save a page as a file - #49

Merged
StefanoGuerrini merged 5 commits into
mainfrom
feat/crawl-discovery-and-page-download
Oct 16, 2026
Merged

StefanoGuerrini merged 5 commits into
mainfrom
feat/crawl-discovery-and-page-download

Conversation

@zenrows-dev-pool

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

Copy link
Copy Markdown
Contributor

Summary

A crawl reports discovery, the strategies it found URLs with, and a create
can ask for them. Reading a page takes download=True to get it as a file to
save. CrawlErrorCode gains api_key_cap_reached. The client dropped all
three, so a caller could not see or set what the API already accepts.

What the task asked for

The client had drifted from the API it wraps. Three things the API serves were
missing here. The discovery parameter and the field echoing it, the
download query parameter on a page read, and one run error code. Markdown
output needed nothing: this client already had it.

How the check confirmed it

uv run ruff check, uv run ruff format --check, uv run ty check src and
uv run pytest all pass, 249 tests. This change adds three checks to the Crawl
acceptance suite: 22 for discovery, 23 for content(download=True), 24 for
the new error code.

A running Crawl service answered the same way. A create with
discovery=["links"] came back with ["links"], and so did a create that left
it out. A create with output_format="markdown" came back with markdown. A
response carrying no discovery at all still parsed.

Encoded by: acceptance checks 22, 23 and 24.

How to try it

crawl = client.create("https://example.com/", discovery=["links"], max_items=5)
print([d.value for d in crawl.discovery])          # ['links']
page = client.content(crawl.crawl_id, result, download=True)

discovery reads ['links'] whether or not you pass it. download=True
returns the same text, asked for as a file to save.

Provenance

discovery is optional on the response model with a default of ["links"],
not required. The API documents that default. An optional field also parses an
answer from a server that does not send it yet. A required one would reject the
whole response instead.

DiscoveryStrategy lists links only, and is open like every other enum here,
so a value this version does not list keeps its raw string.

The lock records the API description this surface follows. It lists
GET /healthz and POST /internal/v1/crawls/{crawl_id}/stop as pending. Both
are in the description and callers cannot reach either. Listing them keeps a
change to either from reading as drift here.

Review

Severity File and line Summary
Medium models.py:107 A required discovery broke 16 tests and would reject any response without the field. Fixed before commit: the field is optional with the documented default.

One pass, by the author. Human must verify: that the default of ["links"] is
the right reading for a crawl created before discovery existed.

Deviations

None.

Record owed

None.

… a page as a file

A crawl reports `discovery`, the strategies it finds URLs with on each
page it walks, and a create can ask for them. `links` follows the links
in the page's HTML, and is what a crawl runs with when the create leaves
it out. The field reads `["links"]` when the server does not send it, so
the model parses an older answer too.

`content(..., download=True)` asks for the page as a file to save rather
than one to show. The text is the same; the server sends a file name
built from the content id.

`CrawlErrorCode` gains `api_key_cap_reached`, which a crawl ends with
when the key that created it reaches one of its credit caps. It parsed
as an unknown value before.
… stopped one has none

The check that reads a page as a file borrowed the crawl another check stops
mid-flight. That stop can land before any page is fetched, and the check then
skipped instead of covering anything. It now falls back to a one-item crawl of
its own and waits for it, as the check beside it already did.
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 quickstart leaves the base URL out, so a run against another base
URL still sent that one crawl to production. The harness now adds the
run's base URL to it, and its start URL pattern matches the README again.
@StefanoGuerrini
StefanoGuerrini marked this pull request as ready for review October 15, 2026 14:59
@StefanoGuerrini
StefanoGuerrini self-requested a review October 15, 2026 14:59
The values that the json output and pagination discovery use stay off for every user. The lock now records them as held, so the drift check reads their flag on each pass.
@StefanoGuerrini
StefanoGuerrini merged commit 8f1aa10 into main Oct 16, 2026
2 checks passed
@StefanoGuerrini
StefanoGuerrini deleted the feat/crawl-discovery-and-page-download branch October 16, 2026 07:04
@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