Skip to content

fix: address code review findings from PR #5 - #6

Merged
siva01c merged 3 commits into
mainfrom
fix/pr5-review-findings
Aug 31, 2026
Merged

siva01c merged 3 commits into
mainfrom
fix/pr5-review-findings

Conversation

@siva01c

@siva01c siva01c commented Aug 31, 2026

Copy link
Copy Markdown
Owner

Follow-up to #5, fixing seven findings from a code review of its two commits (ca106a7 load-shaping CLI flags, c1f1581 report snapshot scoping). Three thematic commits, each independently green.

1. Incremental crawls collapsed every report — 283f5f8

The severe one. --incremental writes only the URLs it re-fetched, so its date folder is a delta, not a picture of the site. Scoping a report to "the newest date folder" pushed every unchanged page into stale and shrank a 300-page report to the handful of URLs the delta touched — and pages.length === 0 never tripped, so the near-empty report looked valid. --all-crawls was the only escape hatch, undocumented for this case.

Each crawl now writes _crawl-meta.json (mode: full | incremental) into its date folder; the merge step stamps _metadata.crawlMode onto every record; the snapshot anchors on the newest full crawl with later incrementals layered on top:

📄 example.com — snapshot 12-08-2026 … 28-08-2026 (+2 incremental crawls): 311 pages

Folders with no manifest read as full, so existing datasets are unaffected — verified on a real 9-crawl dataset, byte-identical output before and after.

2. Delay-range and asset-blocking guards — 0c2c584

  • --delay-min=0 disabled all pacing. The guard tested requestDelayMin for truthiness, so a legitimate 0 lower bound read as "unset" and produced a heavier crawl than the crawler.yml default.
  • No validation of the two bounds. --delay-min=5000 --delay-max=1000 made getRandomDelay() multiply by a negative span; a delay above requestHandlerTimeoutSecs aborted the handler as a timeout and retried — the "more load, not less" failure this PR's own docs warn about. normalizeDelayRange() swaps and clamps, warning on both, and runs on the effective pair (overriding one side inverts the range against the YAML value for the other).
  • --block-assets blocked .json. crawlee's blockRequests() forwards patterns to CDP Network.setBlockedURLs, which matches substrings, so .js also swallowed .json endpoints. A page fetching content over JSON was recorded as an empty 200 and flagged broken by every SEO check. Replaced with a Playwright route filtering on resourceType. Verified against Chromium: .js/.css/.png blocked, .json served, JSON-driven content intact.

3. Reports now carry their crawl — 43c8a46

  • report-seo-issues and report-link-graph-issues stamped filenames with today's date and wrote no scope metadata, so a saved report from a months-old crawl looked current. Both now embed a crawl_scope block and stamp filenames with the crawl date.
  • The 404 report's JSON changed from a bare ReportEntry[] to an object in fix(reports): default report scripts to the latest crawl, not the historical union #5 with no version marker, silently breaking array consumers. All reports now carry schema_version: 2; README documents the break.
  • metaGeneratedVia was added to both locales but never referenced — now wired into the audit header.

Verification

  • npx tsc --noEmit clean; npm test 275 passed (was 258)
  • New tests: crawlManifest.test.ts, delayRange.test.ts, +5 in pageRecords.test.ts
  • tsc + targeted tests green at each commit, so history is bisectable
  • End-to-end on a synthetic incremental fixture: with the manifest 3 pages and the 404 correctly reported; without it 1 page, 0 entries — the exact regression from finding 1

Known limitation, not addressed here

The manifest is written only on successful completion. A crawl killed mid-run, or cut short by --max-requests, still leaves a partial folder that reads as full. Same symptom, different cause — happy to take it in a follow-up.

🤖 Generated with Claude Code

siva01c and others added 3 commits August 31, 2026 15:11
An --incremental run writes only the URLs it re-fetched, so its date folder is
a delta rather than a picture of the site. Scoping a report to "the newest date
folder" therefore pushed every page that happened not to change into `stale` and
collapsed a 300-page report to the handful of URLs the delta touched — without
tripping the `pages.length === 0` guard, so the near-empty report looked valid.

Each crawl now records a _crawl-meta.json manifest in its date folder, the merge
step stamps `_metadata.crawlMode` onto every record from it, and the snapshot is
anchored on the newest *full* crawl with every incremental crawl since layered on
top. Date folders with no manifest read as `full`, so existing datasets behave
exactly as they did.

seo-audit gets the same rule separately because it loads date folders directly
rather than the merged JSONL.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Three defects in the load-shaping flags added alongside them:

--delay-min=0 disabled pacing entirely. The request-handler guard tested
requestDelayMin for truthiness, so a legitimate 0ms lower bound (--delay-min=0
--delay-max=3000) read as "no delay configured" and produced a faster, heavier
crawl than the crawler.yml default — the opposite of the flag's intent.

The two bounds were never validated. An inverted pair made getRandomDelay()
multiply by a negative span and return values below the requested minimum, and a
delay longer than requestHandlerTimeoutSecs aborted the handler as a timeout and
retried it, adding load rather than shedding it. normalizeDelayRange() now swaps
an inverted pair and clamps to half the handler timeout, warning on both. It runs
on the effective pair, since overriding only one side inverts the range against
the crawler.yml value for the other just as easily as passing both.

--block-assets blocked more than assets. crawlee's blockRequests() forwards its
patterns to CDP Network.setBlockedURLs, which matches substrings, so the '.js'
pattern also swallowed '.json' endpoints and any URL merely containing '.js'. A
page fetching its content over JSON was then recorded as an empty 200 and flagged
as broken by every downstream SEO check. Replaced with a Playwright route that
filters on the browser's own resourceType, so scripts are blocked and XHR/fetch
responses are not. Verified against Chromium: .js/.css/.png blocked, .json served.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The snapshot scoping made reports state their crawl date on stdout, but only the
404 report persisted it. report-seo-issues and report-link-graph-issues still
stamped their filenames with new Date() — today, not the crawl — and wrote no
scope metadata at all, so a saved report built from a months-old crawl was
indistinguishable from one describing the site as it is now. Both now embed a
crawl_scope block (per domain: crawl date, baseline full crawl, incremental
crawls layered on it, mode, pages analysed) and stamp filenames with the crawl
date. Aggregate runs span several crawls and have no single date, so those still
fall back to today.

The 404 report's JSON also changed shape in the previous release without a
version marker: it was a bare ReportEntry[] and became an object, silently
breaking any consumer reading it as an array. All persisted reports now carry
schema_version (2), and the README documents the break from version 1.

Wires up metaGeneratedVia, added to both locales but never referenced: the audit
header now states the crawl scope it was generated from.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@siva01c
siva01c merged commit 3377abf into main Aug 31, 2026
4 checks passed
@siva01c
siva01c deleted the fix/pr5-review-findings branch August 31, 2026 16:13
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