Skip to content

feat: type the code a job site emails into Windrush, and it sends the form - #70

Merged
coderback merged 1 commit into
mainfrom
feat/type-the-emailed-code
Oct 20, 2026
Merged

coderback merged 1 commit into
mainfrom
feat/type-the-emailed-code

Conversation

@coderback

Copy link
Copy Markdown
Owner

Why

Catapult's live send stopped at Greenhouse's "A verification code was sent to … enter the 8-character code to confirm you're a human". Autopilot could only give up and hand over (#67, #69). The user wants to finish such sends from Windrush: they read the code from their inbox and type it in, and Windrush enters it on the form it holds open.

The code always comes from the user. The check is there to confirm a person is applying, so Windrush never fetches a code itself, not even from a connected Gmail inbox. The only way in is the user typing it (POST /autopilot/runs/{id}/code). This is stated where the hook is defined (browser_agent.CodeAsker) and in the endpoint.

What changed

browser_agent

  • _finish_unattended(..., ask_code) handles a submit that stops at a code page (_code_asked). It:
    1. waits for the user's code;
    2. types it into the page's code boxes (_enter_code): one character a box when there's a box each (Greenhouse has eight, maxlength=1), else the one field labelled as a code. It uses CDP insertText, so React-controlled boxes take it like typing;
    3. checks the boxes hold the code;
    4. sends again through the submit agent.
  • A code that doesn't take is asked for again, up to 3 times. No code means the form stays unsent.
  • apply_with_browser passes ask_code through.

autopilot

  • While it waits, the run is awaiting_code, with the site's line and the wait (10 minutes, _CODE_WAIT_S). give_code hands a typed code to the waiting form (_codes: run id → future).
  • Sent: the notes say "Sent after you typed in the code the job site emailed you."
  • No code in time, or none that took: held (needs_you), saying which, with the handover pack.
  • Practice runs never wait (no ask_code).

tracker

  • New outcome awaiting_code. A restart while a form waits holds it, since the browser is gone.

main

  • POST /autopilot/runs/{id}/code: letters and digits, 4 to 12 (spaces and dashes dropped). It returns 409 once the form isn't waiting, and another user's run is out of reach.

Frontend

  • "Needs your code" in the queue and the sidebar count.
  • A "Code from your email" box with "Send code" on the run.

Tests

  • Fast:
    • a wrong code then the right one sends it, asking again in between;
    • no code in time holds it;
    • practice never waits;
    • a restart while waiting holds the run;
    • the endpoint: hands over, 400 and 409, and out of another user's reach.
  • Real browser (new pages code_boxes.html, eight auto-advancing boxes like Greenhouse, and code_field.html): the code is entered and the form sent again, and with no code it's left unsent. ScriptedLLM's submit_success can now be a sequence.
  • E2E smoke: the code typed on a waiting run goes to it.
  • Fast suite: 1082 passed. The related browser tests: 6 passed. Smoke: 17 passed. Frontend tsc and lint clean.
  • Not yet tried on a live Greenhouse form; the first real send will show it.

After deploy

Rebuild both containers with docker compose up -d --build api frontend.

… form

Greenhouse asked Catapult's send for an emailed code "to confirm you're a human",
and the run could only give up. Now a live run holds the filled form open for
the user's code: the run shows "Needs your code" with the site's message and a
box; the code typed there is entered on the form and the application sent.

The code always comes from the user. The check is there to confirm a person is
applying, so Windrush never fetches a code itself, not even from a connected
Gmail inbox; the only way in is the user typing it (POST .../code).

- browser_agent: when the submit stops at a code page and ask_code is given, it
  waits for the user's code, types it into the page's code boxes (one character
  a box, as Greenhouse has eight, or one field labelled as a code; insertText,
  so React boxes take it), checks the boxes hold it, and sends again. A code
  that doesn't take is asked for again, up to three.
- autopilot: the run is "awaiting_code" while it waits, with the site's line and
  how long it waits (10 minutes); give_code hands a typed code to the waiting
  form. Sent: "Sent after you typed in the code…". No code in time, or none that
  took: held as before, saying which, with the handover pack. Practice runs
  never wait.
- A restart while a form waits holds it (the browser is gone).
- POST /autopilot/runs/{id}/code: letters and digits, 4 to 12; 409 once the
  form isn't waiting; another user's run is out of reach.
- Frontend: "Needs your code" in the queue and the sidebar count, with a code
  box on the run.

Tests: fast (a wrong code then the right one sends it; no code in time holds it;
practice never waits; restart; the endpoint), real browser (the code entered in
eight boxes and in one field, then sent; no code leaves it unsent) and the e2e
smoke test (the code typed goes to its run).
@coderback
coderback merged commit dc01ee8 into main Oct 20, 2026
3 of 4 checks passed
@coderback
coderback deleted the feat/type-the-emailed-code branch October 20, 2026 14:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant