Skip to content

lab-ops-hub: redirect policy diverges between crawlers — Worker fallback caps at 5 hops and follows any 3xx, container follows 10 hops and only 301/302/303/307/308 #571

Closed

Description

@dezren39

Summary

The two lab-ops-hub crawlers disagree on redirect policy, so the same public URL can succeed via one crawler and fail via the other — and the Worker fallback (which exists to stand in when the container is unavailable) does not reproduce the container's behaviour.

Evidence (verified 2026-10-22, on main 7a9ac873)

Redirect-count limit

  • Worker fallback: fetchFollowingValidatedRedirects defaults to maxRedirects = 5 (pkgs/lab-ops-hub/src/lib.ts:204, error at lib.ts:216-218).
  • Container: _ValidatingRedirectHandler (pkgs/lab-ops-hub/container/app.py:389-396) subclasses urllib's HTTPRedirectHandler and inherits its defaults: max_redirections = 10, max_repeats = 4.

Redirect status codes

  • Worker fallback follows any 3xx response that carries a Location header (lib.ts: res.status >= 300 && res.status < 400).
  • urllib's handler only treats 301, 302, 303, 307 and 308 as redirects; any other 3xx raises HTTPError, which the container's do_POST surfaces as a 502.

Reproduction (both sides run this run)

  • A 7-hop chain of 302s between public URLs, ending in 200:
    • Worker fallback (real fetchFollowingValidatedRedirects under bun test with a scripted fetcher): Error: too many redirects (more than 5).
    • Container redirect handler (urllib opener against a local server): 200.
  • A single 300 Multiple Choices response with a Location pointing at a public 200 page:
    • Worker fallback: follows it, returns 200.
    • Container: HTTPError 300 → /crawl returns 502.

Why it matters

/api/crawl tries the container first and silently falls back to the Worker on any container error (src/index.ts crawlWithContainer / catch → crawlFallback), recording only source in the result. Redirect-heavy public pages (shorteners, AMP/print chains, locale negotiation) therefore crawl or fail depending on which backend happened to serve the request, and a 6–10-hop page that the container handles fine hard-fails whenever the container is cold or down — exactly when the fallback is supposed to be equivalent. This is the same class of Worker/container divergence fixed for URL validation (#460, #474, #481, #500, #529, #567) and extraction (#569); redirect policy is the remaining unaligned piece.

Acceptance criteria

  • One redirect policy stated in one place both implementations follow: a single hop limit and a single set of followed status codes (suggested: align the Worker to the container's urllib semantics — 10 hops, codes 301/302/303/307/308 — or pick 5 deliberately and enforce it in the container handler too; either way the two must match, including a repeat/loop rule equivalent to urllib's max_repeats).
  • The container handler sets its limits explicitly rather than relying on urllib defaults, so a Python/urllib default change cannot silently re-open the divergence.
  • Tests on both sides pin the shared policy: a chain one hop over the limit fails identically, a chain at the limit succeeds identically, and a non-followed 3xx (e.g. 300/304 with Location) is handled identically (TS tests in pkgs/lab-ops-hub/test/lib.test.ts, Python tests alongside test/test_container_validate.py or a new container redirect test file).
  • Per-hop SSRF re-validation of every redirect target is unchanged on both sides (lab-ops-hub: Worker-fallback crawler follows redirects to non-public targets (no redirect validation, unlike the container) #460).

Activity

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    priority/normalOrdinary priority (the default)
    effort/mMedium: expected under 1 day of work
    meta/enrichedIntake review done: acceptance criteria, links, and normalized labels added
    on Oct 26, 2026
  2. dezren39 commented on Oct 26, 2026

    @dezren39
    MemberAuthor

    Intake review

    Still live (verified this run, fleet monitor): on current code main, pkgs/lab-ops-hub/src/lib.ts:231 still defaults the Worker fallback's fetchFollowingValidatedRedirects to maxRedirects = 5, while the container's _ValidatingRedirectHandler in pkgs/lab-ops-hub/container/app.py still inherits urllib's defaults (10 hops, only 301/302/303/307/308 treated as redirects). The divergence the issue reproduces — a 7-hop chain and a 300-with-Location — is unchanged.

    Related: the lab-ops-hub Worker/container divergence series: URL validation (#460, #474, #481, #500, #529, #567 line) and extraction (#569); #588 is the remaining A-label item from the same family. This is the last unaligned redirect-policy piece, per the issue body.

    Notes / risks: /api/crawl falls back silently, so the divergence is user-visible only as inconsistent crawl success — the fix should state the single policy in one place both implementations reference, as the acceptance criteria require, rather than tweaking one constant. Aligning the Worker up to the container's 10 hops slightly widens fallback fetch behaviour; that is the container's already-shipping policy, not a new exposure.

    Suggested approach: align the Worker to the container's urllib semantics (10 hops; follow only 301/302/303/307/308, other 3xx surfaces as an error), and add the issue's two reproduction cases (7-hop 302 chain, single 300) as tests on both sides.

    Labels normalized by the fleet monitor: kind/bug, source/pr-diff-review, status/needs-triage, priority/normal, effort/m, app/lab-ops-hub.

  3. dezren39 commented on Oct 26, 2026

    @dezren39
    MemberAuthor

    Correction to the intake review above (line number only)

    The fleet monitor's Intake review cited the Worker fallback default at pkgs/lab-ops-hub/src/lib.ts:231. Fresh re-verification this run puts it at lib.ts:210 (maxRedirects = 5, with the limit error at :222-223). The substance of the review is unchanged: the Worker still defaults to 5 hops while the container inherits urllib's 10-hop policy, and the issue remains live. The wrong line number came from a stale tool read caught in this run's correction pass.

  4. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed on Nov 1, 2026
  5. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  6. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

  7. 3 remaining items

  8. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    work/assignedQueue worker lifecycle state
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 1, 2026
  9. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  10. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

  11. added a commit that references this issue on Nov 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

effort/mMedium: expected under 1 day of workkind/bugSomething isn't working (prefixed form of the classic bug label)meta/enrichedIntake review done: acceptance criteria, links, and normalized labels addedpriority/normalOrdinary priority (the default)source/pr-diff-reviewFiled by the pr-diff-review jobstatus:done

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions