Resumes
The model only sees what we chose
Aaron at Job Umbrella · September 18, 2026 · 9 min read
In July I wrote that the hard part of tailoring a resume is not making it fit, it is not lying. I had a fit check for that, which catches an overreach after the fact. This week the problem got solved at the other end, and the fix is structural rather than a better instruction.
Here is what the customizer used to do, and what nearly every tool in this category still does. Take the person’s whole work history. Take the job description. Hand both to a model and ask it to emphasize what is relevant.
“Emphasize what is relevant” is a request. A model can decline a request, or interpret it generously, and generous interpretation is exactly how a tool you never used appears on your resume.
Selection in code, rewriting by the model
Now the selection happens before the model is called at all, in ordinary code that I can read and test.
Every bullet in the profile is scored against the job description. Five are kept from the most recent role, three from the one before, two from the ones before that — more for an entry-level document, fewer for an executive one, never zero from any role, and every role in the history is represented. Skills are ordered by evidence: named in the job description first, then skills the market asks for under those titles, then related ones, capped at a sensible number.
The model receives only that selection. Which means it cannot resurrect a line it was never handed, and it cannot claim a skill that was not on the list, because it never saw one. Its remaining job is to rewrite what it was given so it reads well for this posting. It may match the posting’s vocabulary only where the meaning is identical, and every one of those word swaps is reported back to you with a Revert button beside it.
That is honesty by construction rather than by instruction, and it is a much better engineering position to be in. I am not asking a model to be careful. I am not giving it the material it would need to be careless.
The page shows its work as well: how many of the job’s terms your document covers, which bullets were kept from which role and what each one matched, and every substitution with the original beside it. Run against my own profile it kept 26 of 30 bullets, covered 5 of 5 job terms, made no wording swaps at all, and the fit check still caught the one tool I genuinely do not have.
A score that stopped punishing thoroughness
The profile review had a design flaw that took me a month to see, and it directly contradicted the advice the product gives.
The score was 100 minus a deduction for every flagged item. So a thorough profile with a hundred bullets and four weak lines scored zero, while a thin profile with ten bullets and one weak line scored 75. It was punishing the exact behaviour the profile exists to encourage — put everything in, it is the superset, the resume is the distillation.
It is a rate now: the weighted share of your bullets that pass, with recent roles counted more heavily. Points come off only for having too little — fewer than ten skills, a role in the last decade with fewer than three bullets, a role in the last five years with no number anywhere in it. Add more good material and the score goes up. Add more material of any kind and it is no longer automatically punished.
Alongside it, a profile now shows what employers ask for under each of your target titles, as chips you can tap to add — the gap between your profile and that title, rather than a generic list. That costs one model call per distinct title ever, cached across the whole site, so it is free to you and bounded for me.
The July mistake, finally undone
In July I put the database migration step inside the web service’s start command. In September it came close to taking the site down, and I patched that migration rather than the arrangement. This week the arrangement came out, and the interesting part is how carefully it had to be done.
The change itself is four lines of configuration: migrations move into a dedicated step that runs before the deploy, and the web service just starts the server. The payoff is that a bad migration now fails the deploy, and the platform keeps the previous version serving traffic. Under the old arrangement it produced a service that never boots, which looks like an outage rather than a release to roll back.
The rule I set for myself: a release that changes how migrations run carries no migration of its own. Split it. So the mechanism went to the development environment alone, with no code, and was proven twice — once finding nothing to do, and once made to apply a genuinely pending migration, because those are different things and only the second one proves the mechanism works. Then the same change on production, with every environment variable counted before and after. The site served normally through the whole thing.
Only after that did this week’s actual release go out, with its four migrations, through the path that had now been proven three times.
Review the fix, not just the finding
A third security review ran before the merge. It found one high-severity issue: the code that strips HTML from a job page used patterns whose cost grows quadratically, so a one-megabyte page held the server busy for 14 seconds — from a route with no rate limit on it. It is linear now, capped, and metered. Fourteen seconds became seventeen milliseconds.
More useful than the finding: three of the five medium issues were inside fixes shipped the previous week for the previous review. The worker thread left one step on the main thread. The attempt counter refunded calls that had already been billed. A cache was keyed on one string and prompted with another, which let one tenant’s data answer another’s request.
Every one of those was written by somebody confident they were closing a hole. Fixes need the same suspicion as features, and I now review them that way on purpose.
Release notes
September 18, 202632 commits · 4 migrations · merge 441d5f2, then the dependency bump alone as e382c3fShipped — resumes
- The customizer selects bullets and skills in code, then the model rewrites only what it was given.
- The resume page shows which terms are covered, what was kept from each role, and every word swap, each with a Revert.
- The profile review scores a quality rate instead of deducting for volume.
- Skills employers ask for under each target title, as chips to tap, cached once per title across the site.
- The profile editor folds and searches a long history so it stays usable.
Shipped — matching and safety
- Listings a provider has stopped returning sink in the ranking, and the top ten are re-checked daily.
- Jooble is asked directly whether a listing is still there.
- Repost churn joins the Ghost Job Score from a shared sighting history.
- Four keyless remote job sources, and the provider credit that had never been rendered.
- Resume parsing runs in a worker thread that is terminated at 8 seconds. The worst main-thread stall measured 1 millisecond.
- Connecting the browser extension asks before it mints a token.
- Rate limits moved into the database, so a second web instance no longer multiplies every limit.
Platform
- Migrations run in a pre-deploy step, not in the web start command. A bad migration now fails the deploy instead of the boot.
- Proven on the development environment twice, including one run that applied a real pending migration, then on production.
- Four migrations applied in 150 milliseconds. The service was ready in 1.1 seconds and answered every poll.
Security
- A quadratic pattern in the job-page stripper held the event loop for 14 seconds. Now 17 milliseconds, capped and metered.
- Three of five medium findings were inside the previous week's own fixes.
- A cache keyed on one string and prompted with another could answer across tenants.
- Billed AI calls were being refunded on both meters.
Everything here is free to use
AI resumes and cover letters, a fit check against any posting, search across 12 job boards plus government sites, and a Board that tracks every application. No employer can pay to reach the top of your list.