Summary
Lever postings (*.lever.co) gate application submission behind a human-verification CAPTCHA (FriendlyCaptcha / hCaptcha-style "puzzle") that the headless Playwright agent cannot clear. Auto-Apply is therefore disabled for Lever for now:
- Frontend (
dashboard/src/components/Pipeline.tsx) — the Auto-Apply button is hidden for lever.co links and replaced with an "apply via Open" note (isLever()).
- Backend (
mcp-server/src/api/dashboard.ts) — POST /api/runs with agent: auto-apply rejects Lever links with HTTP 422 (detectAts(applyLink) === "lever").
Greenhouse and other ATSes keep Auto-Apply; their CAPTCHA / emailed-code gates already hand off to the user (needsCaptcha → open-apply notice, needsEmailCode → in-console code input).
Why it is hard
Lever embeds a proof-of-work / image CAPTCHA that:
- renders in a sandboxed iframe (FriendlyCaptcha widget / hCaptcha) whose token must be present before the submit button posts;
- is scored against headless + automation signals, so even when the widget is "solved" the token can be rejected;
- its overlay intercepts the submit click, so the current
clickSubmit() path times out.
Existing detection (detectCaptcha() in mcp-server/src/lib/browser-apply.ts) already recognises hCaptcha/reCAPTCHA/Turnstile iframes and bails with needsCaptcha; Lever needs to get past it, not just detect it.
How to solve it (proposed approaches, easiest to hardest)
- Human-in-the-loop handoff (ship first). Treat Lever like the existing
needsCaptcha path but make it first-class: fill every field as a dry run, then surface the live session to the user to solve the puzzle and click Submit themselves. Requires a non-headless / streamable session (or a remote browser the user can drive) rather than tearing down on detection. Lowest risk, no ToS issues.
- Token-solving service. Integrate a CAPTCHA-solving provider such as 2Captcha, CapMonster, or Anti-Captcha — submit the sitekey plus page URL, receive a token, inject it into the widget's response field, then submit. Adds a paid dependency and a per-provider adapter; verify it actually covers FriendlyCaptcha (Lever's current widget), not just hCaptcha.
- Reduce bot signals. Run a non-headless Chromium (Xvfb in the container), use
playwright-extra plus the stealth plugin, a residential proxy, and realistic timing so FriendlyCaptcha's proof-of-work passes without a challenge. Fragile and an arms race.
Recommended
Start with option 1 — wire Lever into the same review-then-handoff flow, but stream the live browser (the console already polls GET /api/runs/:id/live screenshots) and add an "I solved it — submit" action that calls clickSubmit() on the open session. Then evaluate option 2 for true autonomy.
Acceptance criteria
Related code
detectCaptcha, clickSubmit, fillAndSubmit — mcp-server/src/lib/browser-apply.ts
needsCaptcha / needsEmailCode propagation — mcp-server/src/lib/agent/auto-apply-agent.ts, mcp-server/src/api/dashboard.ts
- Console UI (captcha/code handling) —
dashboard/src/components/Agents.tsx
Summary
Lever postings (
*.lever.co) gate application submission behind a human-verification CAPTCHA (FriendlyCaptcha / hCaptcha-style "puzzle") that the headless Playwright agent cannot clear. Auto-Apply is therefore disabled for Lever for now:dashboard/src/components/Pipeline.tsx) — the Auto-Apply button is hidden forlever.colinks and replaced with an "apply via Open" note (isLever()).mcp-server/src/api/dashboard.ts) —POST /api/runswithagent: auto-applyrejects Lever links with HTTP 422 (detectAts(applyLink) === "lever").Greenhouse and other ATSes keep Auto-Apply; their CAPTCHA / emailed-code gates already hand off to the user (
needsCaptcha→ open-apply notice,needsEmailCode→ in-console code input).Why it is hard
Lever embeds a proof-of-work / image CAPTCHA that:
clickSubmit()path times out.Existing detection (
detectCaptcha()inmcp-server/src/lib/browser-apply.ts) already recognises hCaptcha/reCAPTCHA/Turnstile iframes and bails withneedsCaptcha; Lever needs to get past it, not just detect it.How to solve it (proposed approaches, easiest to hardest)
needsCaptchapath but make it first-class: fill every field as a dry run, then surface the live session to the user to solve the puzzle and click Submit themselves. Requires a non-headless / streamable session (or a remote browser the user can drive) rather than tearing down on detection. Lowest risk, no ToS issues.playwright-extraplus the stealth plugin, a residential proxy, and realistic timing so FriendlyCaptcha's proof-of-work passes without a challenge. Fragile and an arms race.Recommended
Start with option 1 — wire Lever into the same review-then-handoff flow, but stream the live browser (the console already polls
GET /api/runs/:id/livescreenshots) and add an "I solved it — submit" action that callsclickSubmit()on the open session. Then evaluate option 2 for true autonomy.Acceptance criteria
markApplied).Pipeline.tsx(isLever) and remove the 422 guard indashboard.ts.Related code
detectCaptcha,clickSubmit,fillAndSubmit—mcp-server/src/lib/browser-apply.tsneedsCaptcha/needsEmailCodepropagation —mcp-server/src/lib/agent/auto-apply-agent.ts,mcp-server/src/api/dashboard.tsdashboard/src/components/Agents.tsx