Skip to content

fix(crawler): persist metadataOnly flag across resume (#369) - #378

Merged
YusukeHirao merged 2 commits into
devfrom
worktree-eager-greeting-pike
Sep 12, 2026
Merged

fix(crawler): persist metadataOnly flag across resume (#369)#378
YusukeHirao merged 2 commits into
devfrom
worktree-eager-greeting-pike

Conversation

@YusukeHirao

Copy link
Copy Markdown
Member

Summary

Fixes #369.

--list/--list-file(非再帰)クロールでは、非再帰モードで発見された内部アンカーは
LinkList#add(url, { metadataOnly: true }) で「タイトルのみ取得」としてキューに積まれる。
この metadataOnly フラグはメモリ上の Set にしか存在せず、--dedupe-cap の拒否と
auto-retry(#350)が組み合わさって resume が走ると、LinkList#resume() が復元後の
URL を metadataOnly 情報なしで再登録してしまい、リストに含まれない内部ページが
is_target=1(フルスクレイプ)として content_items に書き込まれてしまっていた。

  • content_items.is_metadata_only 列を新設して永続化。書き込みは replaceAnchorEdges
    から resolveContentItemId 経由の1箇所のみ(!options.recursive || anchor.isExternal
    という processAnchors と同じ式を再計算)
  • getCrawlingState()pendingMetadataOnlyis_metadata_only=1 の pending URL)を
    返すようになり、Crawler#resume() / LinkList#resume() がそれを使ってメモリ上の
    metadataOnly フラグを復元
  • root/list URL が別ページのアンカー経由で誤って is_metadata_only=1 になるケースに対して、
    resume 直前に list/config.roots を使って除外する防御を追加

Test plan

  • yarn build
  • yarn lint
  • yarn test(5000件超、既存 + 新規)
  • 新規/更新テスト:
    • create-entity-tables.spec.ts: 新規列のデフォルト値
    • migrate-content-items-is-metadata-only.spec.ts: マイグレーションの冪等性・旧アーカイブ対応
    • resolve-content-item-id.spec.ts: INSERT/UPDATE時の書き込み・ラチェット挙動
    • database.spec.ts: pendingMetadataOnly の導出(recursive/anchor組み合わせ)
    • link-list.spec.ts: resume() でのフラグ復元
    • crawler.spec.ts: resume後の実際のスクレイプ挙動(title-only vs full)の回帰テスト
    • crawler-orchestrator.spec.ts: 6経路(auto-retry, append, inventory, recrawl,
      retryFailed, static resume)すべてでの pendingMetadataOnly 配線 + root除外
    • archive.spec.ts: Archive.setPage() レベルでの recursive 配線の統合テスト

スコープ外(既知の事項として記録)

  • isMetadataOnly() のキー正規化不一致(withoutHashAndAuth保存 vs withoutHash参照)
  • dedupe-cap拒否anchorがplaceholder行を残す点自体
  • is_metadata_only を書き込み時点でroot URLごと完全に除外する再設計(現状は resume 直前の
    読み取り側除外で対応。ARCHITECTURE.md に既知のギャップとして記載済み)

🤖 Generated with Claude Code

YusukeHirao and others added 2 commits September 12, 2026 09:40
Fixes #369. LinkList#add(url, { metadataOnly: true }) only tracked the
flag in memory, so LinkList#resume() re-added every restored URL with
no options — a URL queued for a lightweight metadata-only scrape before
an interruption silently became a full-scrape target once the crawl
resumed. This surfaced most visibly when --dedupe-cap rejections and
issue #350's auto-retry loop combined: pages never listed by the
operator ended up fully scraped and written to content_items.

Persists the decision as content_items.is_metadata_only, written by
replaceAnchorEdges (which recomputes !recursive || anchor.isExternal,
the same formula processAnchors uses) via resolveContentItemId's new
isMetadataOnly option. getCrawlingState() now returns the pending
subset flagged this way, and Crawler#resume() / LinkList#resume()
thread it through to restore the in-memory flag exactly as it was
before the interruption.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CI on #378 caught a real regression: append.e2e.ts's "appended root is
recorded as a fully-internal page" test failed because a URL that was
external (or non-recursive) in an earlier session can carry a stale
content_items.is_metadata_only=1 even after --append promotes it into
mergedRoots. Crawler#resume() runs before the new roots are added via
LinkList#add(), so the dedup in add() let the stale flag silently win
over the root's full-scrape intent — the same class of bug #369 fixes,
just via a different path (append promoting a URL to root instead of
resume restoring pending state).

Re-parses mergedRoots to withoutHashAndAuth and excludes them from the
metadataOnlyUrls passed to Crawler#resume(), mirroring the existing
root-exclusion in #crawlUntilPendingClears and CrawlerOrchestrator.resume.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@YusukeHirao
YusukeHirao merged commit a46220a into dev Sep 12, 2026
10 checks passed
@YusukeHirao
YusukeHirao deleted the worktree-eager-greeting-pike branch September 12, 2026 01:52
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.

--list クロールで dedupe-cap 拒否 + auto-retry 後にリスト外ページがフルスクレイプされる

1 participant