I automated my job search. Here's what actually works.
Six months ago I started automating my job search. Not "use AI to polish my resume" automation — real automation. A pipeline that scrapes job listings, scores them against my profile, writes cover letters, and submits applications.
Here's where I am, what broke, and what actually works.
The pipeline
The system has about eight moving parts:
Ingest. Four scrapers: HN "Who is Hiring" (monthly), Indeed (daily via Playwright), RemoteOK (weekly), email alerts piped through a classifier. Everything lands in a DynamoDB table with a status field: new when first ingested, surfaced after the weekly digest shows it to me, ready_for_review if it needs a browser (Indeed's ATS links), applied, bad_fit, skipped_bg_risk.
Scoring. Each listing gets a score from 0-100. Weights come from a config I've tuned over six months. The critical ones: remote-first (required), ML/infra/fullstack experience, company stage, team size. Listings below 65 don't get processed. Listings flagged for background check requirements get quarantined into skipped_bg_risk — that's a Chester-level decision, not the system's.
Application writing. Claude Haiku reads the job posting and either writes a cover letter or refuses with an explanation. The refusal cases are important: "CANNOT_WRITE: The posting lacks specific signal about the company's technical direction." Haiku refusing is the right outcome there — a generic cover letter would hurt more than help.
ATS submission. A Playwright driver navigates to the actual job application page and submits. This is the hardest part and the biggest remaining bottleneck.
Tracking. Every application gets a record: timestamp, listing source, score, letter digest, submission status. The follow-up system fires at 7 and 14 days.
What failed (the interesting failures)
The source bonus problem. For months the pipeline had a phantom gap: the scoring module gave 0 points for Indeed-sourced listings, while HN and RemoteOK got +30 for being "curated sources." The effect: 64 fresh Indeed listings would come in and the apply cron would find zero qualifying listings. I found this six weeks in while debugging why the queue kept exhausting. 11 lines out of sync across 5 scoring functions.
The silent session conflict. The bundled Claude CLI exits 1 with empty stdout AND empty stderr when it can't get a session slot because I'm already in an interactive session. This isn't an error you can pattern-match on — the output is literally empty. The apply cron interpreted it as "failed" and marked the listing as bad. 11 consecutive extraction failures in a single batch, all from the same underlying cause. Fixed by detecting the silent exit pattern and routing to DeepSeek instead.
The surfaced pool. The apply cron ran --status new only. It was ignoring surfaced listings — the ones I'd already seen in the digest and not vetoed. 218 surfaced listings were just sitting there. Combined with the rescored pool, that's about 120 additional listings at the 65+ threshold. One flag change: --status new,surfaced.
Deduplication timing. Early on, the same company got applications from me two or three times. The dedup logic was keyed on extracted fields (company name, normalized) that didn't exist yet at the time dedup ran. Three layers of dedup now, all keyed on raw fields from ingest, not extracted fields from Haiku.
What actually works
Haiku as a quality gate. I expected to fight this. Instead, Haiku's refusals are usually right. It refuses generic postings, role mismatches, and postings where it would have to fabricate context to write something honest. The ones it does write are better than what I'd write at 11 PM when I'm supposed to be applying but I'm tired.
The scoring calibration. After six months of tuning, the 65+ pool is the right signal. Listings below 65 are genuinely not good fits. I've overridden twice, applied manually, and regretted both.
The async architecture. Lambda + DynamoDB means the pipeline doesn't block on anything. The scoring cron and the apply cron can both fail, restart, and pick up where they left off. The status machine is the source of truth; the Lambda is stateless.
Current state
- ~152 listings at 65+ threshold for tomorrow's run (after tonight's rescore + surfaced pool addition)
- 3 queued for browser (GitHub, Lively, BlueAlly) — waiting on the Playwright driver
- 80 in bg_risk quarantine — intentional pause; some are 100/100 matches with background check requirements
- 6 applied since the fixes landed
The expected daily rate after tonight's fixes: 50-80 applications. Up from 2.
What's still broken
The browser driver for Indeed is the main gap. Indeed's job pages redirect through a JavaScript wall before landing at the actual ATS. Getting that URL requires a real browser session. I have a Playwright driver that runs on a Mac (the cass-browser box), but SSH authentication to that box is broken and the Mac is sleeping overnight when the cron would need it.
That's a Saturday-morning fix, not an overnight fix.
The meta-point
The system is more useful than manual applications — not because it does the work for me, but because it does the work when I wouldn't. At 2 AM when I should be sleeping. On a Wednesday afternoon when I have actual work deadlines. It catches listings I would have missed and applies within 24 hours of them going up.
The tradeoff: it won't write a brilliant, customized letter for a dream job the way a human at full capacity would. What it will do is write a solid, accurate, non-embarrassing letter at 2x the volume I'd manage manually, consistently, without burning out.
That's the real proposition for AI-assisted job hunting. Not "this will get you the job." More like: "this will get you the applications out, so the pipeline doesn't dry up while you're living your life."
The system is essentially a stripped-down version of what I packaged into the Build Your Own Cass course. The full course includes the memory architecture, the tool pattern, the multi-channel setup — but the job hunt pipeline is a complete standalone example of the same underlying approach: a Claude Haiku agent, a DynamoDB state machine, and a set of scheduled Lambda functions that together do something useful without requiring babysitting.
— Chester