← back to Nationalrealestate
discover: fail-soft run recording (try/catch closeRun) so search-anti-bot errors don't leave zombie 'running' rows that hog the consumer slot
03215463059678213689c4eab65b2b3d83566753 · 2026-07-30 19:03:52 -0700 · steve@designerwallcoverings.com
Files touched
M src/enrich/firm_website_discovery.ts
Diff
commit 03215463059678213689c4eab65b2b3d83566753
Author: steve@designerwallcoverings.com <steve@designerwallcoverings.com>
Date: Thu Jul 30 19:03:52 2026 -0700
discover: fail-soft run recording (try/catch closeRun) so search-anti-bot errors don't leave zombie 'running' rows that hog the consumer slot
---
src/enrich/firm_website_discovery.ts | 19 ++++++++++++++++---
1 file changed, 16 insertions(+), 3 deletions(-)
diff --git a/src/enrich/firm_website_discovery.ts b/src/enrich/firm_website_discovery.ts
index 26e2b2c..c71e25c 100644
--- a/src/enrich/firm_website_discovery.ts
+++ b/src/enrich/firm_website_discovery.ts
@@ -157,6 +157,8 @@ async function main() {
console.log(`[discover] run #${runId} · ${queue.length} firms · jittered 2.5-4s/query`);
let ok = 0, miss = 0, err = 0, consecThrottle = 0;
+ let fatal: any = null;
+ try {
for (let i = 0; i < queue.length; i++) {
const f = queue[i];
const q = `${f.name} ${f.hq_city || ''} ${f.license_state} real estate`.replace(/\s+/g, ' ').trim();
@@ -209,12 +211,23 @@ async function main() {
await new Promise(res => setTimeout(res, 2500 + Math.random() * 1500));
}
+ } catch (e: any) {
+ // Fail-soft: an uncaught search/transport error must NOT skip closeRun (which
+ // would leave a zombie 'running' row that never refreshes freshness and makes
+ // this consumer job perpetually stalest → hogs its reserved loop slot).
+ fatal = e;
+ console.error(`[discover] loop aborted, recording partial: ${String(e?.message || e).slice(0, 120)}`);
+ }
+
+ // Always close the run. 'ok' if it processed anything (partial anti-bot failures
+ // are normal for HTML search scraping); 'failed' only on a total pre-loop wipeout.
+ const status = (ok || miss || err) ? 'ok' : 'failed';
await query(
- `UPDATE ingest_runs SET finished_at = NOW(), status = 'ok', rows_upserted = $2, rows_skipped = $3,
+ `UPDATE ingest_runs SET finished_at = NOW(), status = $5, rows_upserted = $2, rows_skipped = $3,
notes = notes || $4 WHERE id = $1`,
- [runId, ok, miss, ` · ${ok} found, ${miss} no-result, ${err} search-errors`],
+ [runId, ok, miss, ` · ${ok} found, ${miss} no-result, ${err} search-errors${fatal ? ` · partial(${String(fatal?.message || fatal).slice(0, 40)})` : ''}`, status],
);
- console.log(`[discover] done · ${ok} found · ${miss} no-result · ${err} errors`);
+ console.log(`[discover] done · ${ok} found · ${miss} no-result · ${err} errors${fatal ? ' (partial)' : ''}`);
await pool.end();
}
← 908d603 Loop: reserve >=1 slot/tick for fast-consumer streams (homes
·
back to Nationalrealestate
·
discover: mark run 'failed' when every firm errored (ok iff b569855 →