Skip to content

fix: stop a dead model reading as an empty page, and a dead link as email - #26

Merged
ralyodio merged 1 commit into
mainfrom
worktree-fix-silent-crawl-extraction
Aug 13, 2026
Merged

ralyodio merged 1 commit into
mainfrom
worktree-fix-silent-crawl-extraction

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Two production failures that both reported success.

The URL box "does nothing"

Pasting a company URL on /prospects has been producing no prospects. The input is there and the whole path works — I ran one through the live site end to end and the job completed — but nothing comes out.

The Anthropic key on the Railway service is spend-capped (400 … You will regain access on 2026-09-01), so the model pass fails on every crawl. extractWithModel swallowed the error and returned empty, which is byte-identical to "this page names nobody" — the correct, common answer for a homepage. So runCrawlJob completed the job, the batch reported Finished — 1 of 1, and the list stayed empty with no error anywhere.

Live log from the crawl I ran:

crawl https://profullstack.com/: Profullstack, Inc. — Web Development Agency, 0 people (links)
jobs: 1 done, 0 retrying, 0 failed, 0 reclaimed

(links) is the deterministic half on its own — the model never contributed, and nothing said so.

An outage is not an answer about the page. extractWithModel now reports unavailable, SiteProvider.crawl carries it as extractionUnavailable, and runCrawlJob throws when a page yielded nobody because nobody read it. The reason lands in last_error, which BatchProgress already renders per failed URL, and the existing backoff retries it once a key works. A refusal or an unparseable reply still completes — those are answers about this page, and asking again gets the same one.

Verification emails point at localhost

APP_URL was unset on the service, so apps/api/src/app.ts fell back to its hardcoded http://localhost:8080 — and RESEND_API_KEY/EMAIL_FROM are set, so real signups were really being mailed a link to their own machine:

http://localhost:8080/verify?token=3015e944…

Nobody could confirm an address, and verification gates every model-backed action. A send failure is audited; a delivered mail is not, so nothing surfaced it.

I set APP_URL=https://outreachgraph.com on the Railway service, which fixes it now. This PR removes the trap: the fallback derives from RAILWAY_PUBLIC_DOMAIN, so any Railway deploy is correct without the variable, and an environment that still cannot work it out says so at boot instead of silently mailing localhost.

Still needed, not in this PR

The extraction stays dead until a working key exists. GEMINI_API_KEY is set on no service in the project, so the fallback chain added in #25 is inert — the chain is anthropic alone. Setting it is the whole unblock; the code already supports it and returns to Claude on its own once the cap lifts on 1 Sep.

Tests

501 pass, 0 fail. Added: an outage is distinguishable from a refusal; a dead model surfaces on the crawl result; a page nobody could read retries with the reason in last_error rather than reporting success.

🤖 Generated with Claude Code

…mail

Two failures that both reported success.

Pasting a URL on /prospects has been producing nothing since the Anthropic
key hit its spend cap. The crawl fetches the page, the deterministic pass
finds no people on it — which is normal, most homepages name nobody — and the
model pass that exists to read those pages throws and returns empty. Empty is
indistinguishable from "the page names nobody", so the job completes, the
batch reports "Finished — 1 of 1", and the prospect list stays empty with no
error anywhere. From the outside the URL box looks like it does nothing.

An outage is not an answer about the page. `extractWithModel` now says which
one happened, `crawl` carries it, and `runCrawlJob` throws when a page yielded
nobody because nobody read it. That puts the reason in `last_error`, where the
batch view already renders it, and lets the existing backoff retry once a key
works again. A refusal or an unreadable reply still completes: those are
answers about this page and asking twice gets the same one.

The second: APP_URL was unset in production, so every verification email
carried `http://localhost:8080/verify?token=…`. Nobody could confirm an
address, and verification gates everything that costs money. A send failure is
audited; a delivered mail is not, so nothing said so. Railway supplies the
public hostname, so the fallback is now derivable rather than a localhost
guess, and an environment that still cannot work it out says so at boot.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ralyodio
ralyodio merged commit 09c8181 into main Aug 13, 2026
4 checks passed
@ralyodio
ralyodio deleted the worktree-fix-silent-crawl-extraction branch August 16, 2026 17:33
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