From ed908c91afff8f595fecee5f022b61e1f8332a9f Mon Sep 17 00:00:00 2001 From: Matthew Jackson Date: Thu, 5 Mar 2026 17:28:23 -0800 Subject: [PATCH] fix: audit bugs + better error logging for searcher debugging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bugs fixed: - form_filler.mjs: add missing await on el.evaluate() in getLabel() - analyze_ats.mjs: check job.apply_type instead of non-existent job.easy_apply - status.mjs: fix typo "that run" → "in that run" - README: add missing lock.mjs to project structure Logging improvements: - job_searcher.mjs: log browser creation, login steps, stack traces on error - linkedin.mjs/wellfound.mjs: catch and log navigation failures per keyword - browser.mjs: descriptive errors for Kernel and CDP connection failures Co-Authored-By: Claude Opus 4.6 --- README.md | 1 + analyze_ats.mjs | 2 +- job_searcher.mjs | 7 +++++++ lib/browser.mjs | 14 ++++++++++++-- lib/form_filler.mjs | 2 +- lib/linkedin.mjs | 7 ++++++- lib/wellfound.mjs | 7 ++++++- status.mjs | 2 +- 8 files changed, 35 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index b5a2eee..fe8fb1c 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,7 @@ claw-apply/ │ ├── linkedin.mjs LinkedIn search + job classification │ ├── wellfound.mjs Wellfound search │ ├── queue.mjs Job queue and config management +│ ├── lock.mjs Process lock to prevent parallel runs │ ├── notify.mjs Telegram notifications with rate limiting │ └── apply/ │ ├── index.mjs Apply handler registry diff --git a/analyze_ats.mjs b/analyze_ats.mjs index 5c93476..fcb8a78 100644 --- a/analyze_ats.mjs +++ b/analyze_ats.mjs @@ -57,7 +57,7 @@ function analyze() { } default: // Count easy apply eligible (status=new on linkedin with easy apply available) - if (job.status === 'new' && job.easy_apply) easyApply++; + if (job.status === 'new' && job.apply_type === 'easy_apply') easyApply++; } } diff --git a/job_searcher.mjs b/job_searcher.mjs index c4fb9b5..1612771 100644 --- a/job_searcher.mjs +++ b/job_searcher.mjs @@ -96,7 +96,9 @@ async function main() { console.log('🔗 LinkedIn search...'); let liBrowser; try { + console.log(' Creating browser...'); liBrowser = await createBrowser(settings, 'linkedin'); + console.log(' Browser connected, verifying login...'); const loggedIn = await ensureLoggedIn(liBrowser.page, liLogin, 'linkedin', settings.kernel_api_key || process.env.KERNEL_API_KEY, settings.kernel?.connection_ids || {}); if (!loggedIn) throw new Error('LinkedIn not logged in'); console.log(' ✅ Logged in'); @@ -125,6 +127,7 @@ async function main() { platformsRun.push('LinkedIn'); } catch (e) { console.error(` ❌ LinkedIn error: ${e.message}`); + if (e.stack) console.error(` Stack: ${e.stack.split('\n').slice(1, 3).join(' | ').trim()}`); } finally { await liBrowser?.browser?.close().catch(() => {}); } @@ -135,7 +138,9 @@ async function main() { console.log('\n🌐 Wellfound search...'); let wfBrowser; try { + console.log(' Creating browser...'); wfBrowser = await createBrowser(settings, 'wellfound'); + console.log(' Browser connected, verifying login...'); const loggedIn = await ensureLoggedIn(wfBrowser.page, wfLogin, 'wellfound', settings.kernel_api_key || process.env.KERNEL_API_KEY, settings.kernel?.connection_ids || {}); if (!loggedIn) console.warn(' ⚠️ Wellfound login unconfirmed, proceeding'); else console.log(' ✅ Logged in'); @@ -164,6 +169,7 @@ async function main() { platformsRun.push('Wellfound'); } catch (e) { console.error(` ❌ Wellfound error: ${e.message}`); + if (e.stack) console.error(` Stack: ${e.stack.split('\n').slice(1, 3).join(' | ').trim()}`); } finally { await wfBrowser?.browser?.close().catch(() => {}); } @@ -182,5 +188,6 @@ async function main() { main().catch(e => { console.error('Fatal:', e.message); + if (e.stack) console.error(e.stack); process.exit(1); }); diff --git a/lib/browser.mjs b/lib/browser.mjs index 7687bae..4be7218 100644 --- a/lib/browser.mjs +++ b/lib/browser.mjs @@ -38,9 +38,19 @@ export async function createBrowser(settings, profileKey) { if (profileName) opts.profile = { name: profileName }; if (kernelConfig.proxy_id) opts.proxy = { id: kernelConfig.proxy_id }; - const kb = await kernel.browsers.create(opts); + let kb; + try { + kb = await kernel.browsers.create(opts); + } catch (e) { + throw new Error(`Kernel browser creation failed: ${e.message}`); + } const pw = await getChromium(playwrightPath); - const browser = await pw.connectOverCDP(kb.cdp_ws_url); + let browser; + try { + browser = await pw.connectOverCDP(kb.cdp_ws_url); + } catch (e) { + throw new Error(`CDP connection failed (browser ${kb.id}): ${e.message}`); + } const ctx = browser.contexts()[0] || await browser.newContext(); const page = ctx.pages()[0] || await ctx.newPage(); diff --git a/lib/form_filler.mjs b/lib/form_filler.mjs index 141d5b0..694f941 100644 --- a/lib/form_filler.mjs +++ b/lib/form_filler.mjs @@ -107,7 +107,7 @@ export class FormFiller { } async getLabel(el) { - return el.evaluate(node => { + return await el.evaluate(node => { const id = node.id; const forLabel = id ? document.querySelector(`label[for="${id}"]`)?.textContent?.trim() : ''; const ariaLabel = node.getAttribute('aria-label') || ''; diff --git a/lib/linkedin.mjs b/lib/linkedin.mjs index 770cc24..118795f 100644 --- a/lib/linkedin.mjs +++ b/lib/linkedin.mjs @@ -40,7 +40,12 @@ export async function searchLinkedIn(page, search, { onPage } = {}) { } const url = `${LINKEDIN_BASE}/jobs/search/?${params.toString()}`; - await page.goto(url, { waitUntil: 'domcontentloaded', timeout: NAVIGATION_TIMEOUT }); + try { + await page.goto(url, { waitUntil: 'domcontentloaded', timeout: NAVIGATION_TIMEOUT }); + } catch (e) { + console.error(` ⚠️ Navigation failed for "${keyword}": ${e.message}`); + continue; + } await page.waitForTimeout(PAGE_LOAD_WAIT); let pageNum = 0; diff --git a/lib/wellfound.mjs b/lib/wellfound.mjs index 27b3fb7..13e8ff4 100644 --- a/lib/wellfound.mjs +++ b/lib/wellfound.mjs @@ -23,7 +23,12 @@ export async function searchWellfound(page, search, { onPage } = {}) { for (const keyword of search.keywords) { const url = `${WELLFOUND_BASE}/jobs?q=${encodeURIComponent(keyword)}&remote=true`; - await page.goto(url, { waitUntil: 'domcontentloaded', timeout: SEARCH_NAVIGATION_TIMEOUT }); + try { + await page.goto(url, { waitUntil: 'domcontentloaded', timeout: SEARCH_NAVIGATION_TIMEOUT }); + } catch (e) { + console.error(` ⚠️ Navigation failed for "${keyword}": ${e.message}`); + continue; + } await page.waitForTimeout(SEARCH_LOAD_WAIT); // Scroll to bottom repeatedly to trigger infinite scroll diff --git a/status.mjs b/status.mjs index 12a2703..a418a58 100644 --- a/status.mjs +++ b/status.mjs @@ -127,7 +127,7 @@ function formatReport(s) { ? `🔄 Running now` : `⏸️ Last ran ${timeAgo(ar.last_run?.finished_at)}`; const lastApplierDetail = ar.last_run && !ar.running - ? ` Applied ${ar.last_run.submitted} jobs that run` + ? ` Applied ${ar.last_run.submitted} jobs in that run` : null; const lines = [