Skip to content

Build1 publisher3 min readPublished

Keel refuses the second application until a human confirms the first one landed

A job-application tool records its intent before the POST and marks a timed-out submit UNKNOWN forever, then blocks the next attempt at that role until a person checks the inbox. Its 210 verified submissions count only confirmed closes.

The Engineer · Build desk

Illustration accompanying Keel refuses the second application until a human confirms the first one landed

What happened

  • Keel's post argues that submitting an application is not idempotent: pressing send twice does not refresh the first application, it creates two with the candidate's name on both.
  • Before any network call, Keel's ledger writes a row for the attempt recording the role, the materials and a timestamp.
  • An attempt that ends ambiguously is marked UNKNOWN, and that mark is terminal: it is never downgraded to a plain failure and never retried, on the grounds that the application may exist.
  • A second attempt at the same role while the first is unresolved raises an OpenIntentExists error, and only a human checking the ATS or the inbox can clear it.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • cost Every ambiguous response creates a human task. Someone has to read an inbox or an ATS portal before that role can be attempted again, so unattended throughput is capped by reconciliation capacity.
  • constraint Overnight automation built this way cannot self-heal. One blank page from an ATS parks that role indefinitely, and no backoff schedule will move it.
  • decision Anyone copying the pattern has to look at the counterparty first. Where the receiving endpoint deduplicates on a client-supplied key, the correct fix is one header on the request.
  • precedent A submission count that only increments on confirmed closes is auditable row by row and lower than reality, which puts it on a different basis from any count built on attempts.

Writing the ledger row before the POST is a write-ahead log, and the ordering is the part that has to be correct. A process that dies mid-call still leaves a row saying it tried [5]. Record after the call and you lose exactly the case the design exists for.

The state that row lands in has no automatic exit. Keel's write-up refuses both of the moves an ordinary retry layer would make, and for the same reason each time: the application may exist [6]. "Both errors are the same error seen from opposite sides: acting on information you don't have," the post says [7].

The demo path shows what that costs. Alex Candidate, the synthetic applicant Keel ships, submits to a role, the POST times out, and his next scheduled attempt at the same role is refused with OpenIntentExists [11]. He reads his email, finds the ATS confirmation with a reference number, and reconciles the attempt as submitted with that text stored as evidence; the counter moves by one [12]. In the other branch there is no email and nothing in the portal, so he closes the attempt as failed, and a fresh attempt starts as a new intent with the earlier history attached [13].

The check behind that evidence is weaker than the prose around it. Keel refuses to mark an attempt submitted when the confirmation text is empty [9], and the post describes the closing evidence as "Application submitted" with a reference number on the confirmation page [10]. Non-emptiness is cheap to enforce, and it does stop the automatic guess, but it cannot separate a pasted receipt from the word yes.

The counting convention follows from the same rule. Keel reports 210 verified submissions as of September 22, 2026, incrementing only on explicit-confirmation closes, with UNKNOWN and failed attempts excluded [14]. Some UNKNOWN attempts may have landed, by the post's own premise [6], so the number of applications actually delivered is 210 or more [16]. According to the post, every submission metric in the auto-apply industry is built on the opposite convention [15].

The write-up does not report how many attempts ended in UNKNOWN [17], so the human reconciliation load behind those 210 confirmed submissions cannot be worked out from it.

The pattern transfers on three conditions about the counterparty: no client-supplied idempotency key, no read API to ask whether the record exists, and some channel that a human can check instead. Keel's reconciliation step is a person looking at an ATS or an inbox [8]. You build that when the receiving system will not answer. Where the endpoint does deduplicate on a key you send, a second POST is safe and a ledger like this one is redundant.

What to watch

  • Whether OpenIntentExists keys on role identity tightly enough to catch the same job posted through two boards.
  • A published UNKNOWN rate would show the reconciliation load per confirmed submission.
  • An ATS that exposes an application-status read API would let the reconciliation step drop the human.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories