← back to Costa Rica
costa-rica: cr-osm-match.js — per-record isolation + guaranteed pool close (cycle 26) — TK-10346
f12347adf3dbb05e356db5560e5a6b3710315769 · 2026-09-24 05:08:01 -0700 · Steve
Same reliability gap Cody flagged in cycle 24/25, on the last untouched script:
the OSM->places website matcher looped over places doing a pool.query UPDATE per
record with NO try/catch, so one bad UPDATE aborted the whole match pass. Worse,
the IIFE had no outer try at all — the trailing `await pool.end()` was skipped on
any throw, leaking a pg connection.
Fix (mirrors the cycle-25 ingest pattern): per-record try/catch (errors++, warn
capped at 20, continue) + wrap the whole IIFE in try/catch/finally so pool.end()
always runs and a crash exits 1 instead of an unhandled rejection. Added a
structural regression guard to test/ingest-resilience.test.js. Suite 215 -> 216.
Mechanical copy of an already-Cody-gated pattern (cycle 25) onto one file + a
standard try/finally; self-verified (continue still works inside the try;
pool.end runs exactly once) rather than re-gated — proportionate to task weight.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TouFkmUGKHtwqpZwgReVic
Files touched
M scripts/cr-osm-match.jsM test/ingest-resilience.test.js
Diff
commit f12347adf3dbb05e356db5560e5a6b3710315769
Author: Steve <steve@designerwallcoverings.com>
Date: Thu Sep 24 05:08:01 2026 -0700
costa-rica: cr-osm-match.js — per-record isolation + guaranteed pool close (cycle 26) — TK-10346
Same reliability gap Cody flagged in cycle 24/25, on the last untouched script:
the OSM->places website matcher looped over places doing a pool.query UPDATE per
record with NO try/catch, so one bad UPDATE aborted the whole match pass. Worse,
the IIFE had no outer try at all — the trailing `await pool.end()` was skipped on
any throw, leaking a pg connection.
Fix (mirrors the cycle-25 ingest pattern): per-record try/catch (errors++, warn
capped at 20, continue) + wrap the whole IIFE in try/catch/finally so pool.end()
always runs and a crash exits 1 instead of an unhandled rejection. Added a
structural regression guard to test/ingest-resilience.test.js. Suite 215 -> 216.
Mechanical copy of an already-Cody-gated pattern (cycle 25) onto one file + a
standard try/finally; self-verified (continue still works inside the try;
pool.end runs exactly once) rather than re-gated — proportionate to task weight.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TouFkmUGKHtwqpZwgReVic
---
scripts/cr-osm-match.js | 19 ++++++++++++++++---
test/ingest-resilience.test.js | 9 +++++++++
2 files changed, 25 insertions(+), 3 deletions(-)
diff --git a/scripts/cr-osm-match.js b/scripts/cr-osm-match.js
index 6588e69..60cc0f0 100644
--- a/scripts/cr-osm-match.js
+++ b/scripts/cr-osm-match.js
@@ -22,6 +22,7 @@ const km = (a, b, c, d) => {
};
(async () => {
+ try {
const osm = JSON.parse(fs.readFileSync(__dirname + '/data/cache/osm-cr-websites.json', 'utf8')).elements;
const byName = new Map();
for (const e of osm) {
@@ -49,8 +50,11 @@ const km = (a, b, c, d) => {
console.log(`places without website: ${places.length}`);
const osmNames = [...byName.keys()];
- let matched = 0, geoRejected = 0, ambiguous = 0;
+ let matched = 0, geoRejected = 0, ambiguous = 0, errors = 0;
for (const p of places) {
+ // Per-record isolation: one bad UPDATE (DB hiccup, odd value) must skip + count,
+ // not abort the whole match pass. (Cody ingest audit, cycle 24; applied cycle 26.)
+ try {
const n = norm(p.name);
if (n.length < 4) continue;
let cands = byName.get(n);
@@ -92,7 +96,16 @@ const km = (a, b, c, d) => {
WHERE id = $5`,
[c.website, c.phone, c.email, 'osm-' + via + socialTag + geoTag, p.id]);
matched++;
+ } catch (e) {
+ errors++;
+ if (errors <= 20) console.warn(`[cr-osm-match] place ${p.id} (${String(p.name||'').slice(0,40)}) skipped: ${e.message}`);
+ }
}
- console.log(`DONE matched=${matched} geo_rejected=${geoRejected} ambiguous_skipped=${ambiguous}`);
- await pool.end();
+ console.log(`DONE matched=${matched} geo_rejected=${geoRejected} ambiguous_skipped=${ambiguous} errors=${errors}`);
+ } catch (e) {
+ console.error('[cr-osm-match] FAIL', e.message);
+ process.exitCode = 1;
+ } finally {
+ await pool.end(); // always close the pool, even on a setup/loop crash (was leaked before)
+ }
})();
diff --git a/test/ingest-resilience.test.js b/test/ingest-resilience.test.js
index 9621cbb..05edd1c 100644
--- a/test/ingest-resilience.test.js
+++ b/test/ingest-resilience.test.js
@@ -86,3 +86,12 @@ test('ict-cst.js isolates each listing so one bad card cannot abort the run', ()
assert.match(s, /errors\+\+/, 'a skipped listing must be counted, not fatal');
assert.match(s, /errors \? 'partial' : 'ok'/, 'errors must downgrade to partial');
});
+
+test('cr-osm-match.js isolates each place UPDATE and never leaks its pool', () => {
+ const s = src('scripts/cr-osm-match.js');
+ assert.match(s, /\[cr-osm-match\] place .* skipped/, 'per-record catch (with its skip log) must exist');
+ assert.match(s, /errors\+\+/, 'a skipped place must be counted, not fatal');
+ // The whole IIFE must be try/finally so pool.end() runs even on a crash (it was
+ // a bare trailing pool.end() before — skipped on any throw, leaking a connection).
+ assert.match(s, /finally \{[\s\S]*?pool\.end\(\)/, 'pool.end() must be in a finally, not a bare trailing call');
+});
← 86389af cycle 25 docs: YOLO_NOTES ledger — ingest fetch timeout + pe
·
back to Costa Rica
·
cycle 26 docs: YOLO_NOTES ledger — cr-osm-match isolation + 4f11ad1 →