This release is about saying what career-ops did not read or did not keep. A board it read only in part is logged partial instead of reachable, a template tag the PDF renderer drops gets a warning, and a CV bullet whose verb drifted from the bullet you wrote is blocked.
Highlights
- A board read in part says so. A board that career-ops could read only in part is now logged
partial, notreachable(#6636), and boards that failed on a temporary error get one more try after the sweep (#6675), both by @kellymmcgrath. Before the headed-browser retry, the liveness check reads the site's robots.txt first (#6551 by @benbalter). - Your CV, held to your own facts. A generated bullet whose verb drifted from its source bullet is blocked (#6630 by @kellymmcgrath), structured facts are bound to the records they came from (#6016 by @luochen211), and a build with an unresolved
{{token}}fails instead of printing it (#6408 by @sogav21).cv-templates.mjs lint cv <name> --renderwarns about what the renderer drops in silence, such as scripts and remote fonts (#5943 by @rubicon). - Bring your tracker with you.
import-tracker.mjsimports the CSV export of another tracker (#6170), and the scan keeps a posting's close date and shows it in the pipeline (#6597), both by @mbrow190. - One ask at a time. career-ops asks for a GitHub star once, after your first report, and for the manifesto later, never in the same run. The asks that were spread across scan, doctor and update now live in one place (#6700).
- Twelve more places to look. HiBob, HireHive and ALDI Nord (#6502, #6503, #6529 by @ilyakanevskiy), every Workable tenant through Workable's public XML feed (#6793 by @ilyakanevskiy), Polymer and Trakstar Hire (#6128, #6131 by @mbrow190), Tes (#6731 by @AdilAzhariOmsan), TurboHire (#6712 by @okeken), any employer's job sitemap (#6631 by @kellymmcgrath), Remote's own ATS (#6392 by @artemtrofymenko), ADP Recruiting Management (#6678 by @dgitman) and SEEK NZ (#6488 by @prateek942).
Updating
Run node update-system.mjs apply --confirm in your career-ops folder. If you said no to the update prompt earlier, this is the same command.
Thank you
This release carries 239 pull requests from 58 people, 20 of them contributing for the first time.
- @mbrow190: #5561, #6048, #6058, #6079, #6085, #6088, #6093, #6104, #6108, #6112, #6115, #6125, #6126, #6128, #6131, #6132, #6133, #6134, #6135, #6136, #6137, #6140, #6141, #6170, #6183, #6333, #6550, #6595, #6596, #6597, #6598, #6599, #6600, #6601, #6602, #6611, #6618, #6619, #6669, #6775, #6776, #6783, #6784
- @vliggio: #6223, #6224, #6225, #6275, #6276, #6282, #6283, #6289, #6295, #6530, #6532, #6556, #6576, #6578, #6581, #6587, #6590, #6591, #6620, #6628, #6629, #6652, #6654, #6655, #6709, #6711, #6714, #6715, #6745, #6746, #6748, #6765, #6774, #6787, #6807, #6810
- @abankar1: #5905, #6360, #6390, #6449, #6452, #6463, #6464, #6490, #6491, #6564, #6565, #6566, #6567, #6568, #6584, #6585, #6658, #6659, #6660, #6661, #6662, #6663, #6664, #6762, #6798, #6800
- @dgitman: #6510, #6558, #6559, #6560, #6604, #6606, #6607, #6624, #6670, #6678, #6690, #6692
- @ilyakanevskiy: #6467, #6502, #6503, #6529, #6573, #6575, #6580, #6582, #6790, #6793
- @AdilAzhariOmsan: #6489, #6505, #6533, #6716, #6717, #6718, #6724, #6731
- @artemtrofymenko: #6392, #6433, #6485, #6528, #6688, #6786
- @kellymmcgrath: #6630, #6631, #6636, #6672, #6673, #6675
- @DeryFerd: #6061, #6062, #6769, #6770, #6771
- @okeken: #6657, #6710, #6712, #6735, #6812
- @prabchevski (first contribution): #6483, #6484, #6496, #6501, #6506
- @rubicon: #5934, #5943, #5945, #5961, #5962
- @bercedev: #6419, #6548, #6616, #6617
- @luochen211: #5675, #5729, #6016, #6730
- @benbalter: #6546, #6551, #6552
- @Codexnever: #6473, #6579, #6680
- @ethanyandev (first contribution): #6571, #6572, #6577
- @moelomda: #3948, #3950, #3951
- @roydonsequeira: #6462, #6543, #6687
- @AnkitParekh007: #6384, #6740
- @dianziji (first contribution): #6458, #6656
- @hakarrr (first contribution): #6728, #6739
- @muhammadumer-waheed (first contribution): #6744, #6805
- @ncoprod: #5512, #6022
- @nessuarez: #6676, #6743
- @prateek942: #6488, #6750
- @Schlaflied: #6504, #6789
- @sogav21: #6407, #6408
- @tharun-37: #6588, #6759
- @AayanWasti (first contribution): #6719
- @Abhipatil0065: #6474
- @briacSck: #2550
- @CRIMSONHydra (first contribution): #6521
- @Dakshx07: #6738
- @darkpandawarrior: #6207
- @elmaroun07-hub: #6169
- @faramirezs: #6439
- @Hamzaisadev (first contribution): #6804
- @henrikdukefoss (first contribution): #6733
- @ilmmg (first contribution): #6612
- @Kedar-Khedkar (first contribution): #6593
- @lilysttail (first contribution): #6679
- @MannXo: #5985
- @maxmilian: #6683
- @MdRaf1: #6553
- @monkey1wizard (first contribution): #6531
- @phozzy: #6544
- @PSA-Team-source (first contribution): #6500
- @saintech (first contribution): #6363
- @sarthakmhase2007-ctrl (first contribution): #6802
- @silent-shadow11 (first contribution): #6801
- @SulimanAbdulrazzaq: #5689
- @Tanmay9223: #5804
- @Vijaysir0hi: #6518
- @vk211 (first contribution): #6726
- @w21180239: #6481
- @ZainnQureshii (first contribution): #6773
- @zainulabideen041 (first contribution): #6594
1.40.0 (2026-11-10)
Features
- ask once for a star after the first report, and for the manifesto later (never together) (#6700) (bd7ef1a)
- cli: Add Oh My Pi (omp) host support (#5804) (b273441)
- cv-facts: block bullets whose verb drifted from their source bullet (#1411) (#6630) (cb3e38c)
- cv-templates: add renderLint, a warning for what the renderer silently drops (#5943) (40e0531)
- expand: list document names in the GitHub evidence step (#5728) (#5729) (38892bd)
- i18n: let i18n-drift fail CI with --fail-under (#6771) (c0d44f5)
- intake: snapshot committed sources and diff changed ones (#6552) (24a8b95)
- linkedin: add alert mail on the scan schedule (#6112) (51b16d3)
- liveness: check robots.txt before the headed-browser retry (#6551) (1d74416), closes #5776
- oferta: optional positioning_band beside the score (#4720) (#6789) (d17680d)
- oferta: warn before an evaluation when the GitHub evidence is stale (#5674) (#5675) (16d01ad)
- offer-prep: contract-stage jurisdiction passes for agency licensing, classification and terminology (#3641) (#6504) (b70bb8e)
- pdf: add an opt-in --strict-fact-check flag (#6769) (5e514db)
- providers: add HiBob provider (#6502) (a1b3e60)
- providers: add HireHive provider (#6503) (7c46efe)
- providers: add Polymer careers API provider (#6128) (5910616)
- providers: add single-company provider for ALDI Nord (#6529) (9ecdc30)
- providers: add Tes, UK and international teaching jobs (#6685) (#6731) (95a704b)
- providers: add Trakstar Hire openings provider (#6131) (4fbb15e)
- providers: add TurboHire careers provider (#6712) (ef595b6)
- providers: allow posts_per_page on jobspresso feed URL (#6694) (#6738) (264a13c)
- providers: consider reads the whole board through meta.sequence and retries temporary failures (#6467) (866aaac)
- providers: generic employer job sitemap provider (provider: sitemap) (#6631) (7af08dc)
- providers: page through Google Careers results (#6735) (38c2091)
- providers: read Remote's own ATS at apply.remote.com (#6392) (d5429eb)
- providers: talentlink reads Lumesse TalentLink career sites (#6790) (8387238)
- router: show scan and evaluation progress in the session title (#6586) (#6587) (6555e50)
- scan-ats-full: sweep every Workable tenant from Workable's public XML job feed (#6793) (02e7607)
- scan: add --retain-days to detect-reposts.mjs (#6770) (f48fa4e)
- scan: add ADP Recruiting Management (myjobs) provider (#6678) (7418757)
- scan: add SEEK NZ keyword scanner (#6488) (9c53f00)
- scan: bring back --daterange and pagination in scan-seek (#6694) (#6750) (a18ddb3)
- scan: content_filter min_match and rescue_title_misses (#6020) (#6531) (adeb800)
- scan: keep a posting close date and show it in the pipeline (#6597) (6360bbb)
- scan: report partly-read boards as
partial, notreachable(#6636) (7b0d9b1) - scan: retry boards that failed on a transient error once, after the sweep (#6675) (bf5d0fc)
- templates: add Norway (NO) rows to the fee, agency-licensing and prohibited-content tables (#6628) (fd922ee)
- tracker: add verify-tracker-url-match.mjs (#6061) (e3db0c9)
- tracker: import another tracker's CSV export with import-tracker.mjs (#6170) (ce9dbfd)
- web: sort the inbox and what's new by fit (#6601) (fd5de43)
Bug Fixes
- adoption: let the ladder workflow remove labels from pull requests (#6732) (893e406)
- agent-inbox: exit 1 on an unknown subcommand instead of printing usage at 0 (#6207) (dc514d4)
- apple: keep the country on a location (#6452) (5b7e17a)
- applicantpro: read the close day from the tenant's validThrough shape (#6716) (0874073)
- archive-posting: refuse a Cloudflare challenge page instead of saving it as the JD (#6724) (dc53c8a)
- asks: oferta keeps the whole star ask, drawing included (#6734) (9de9304)
- audit-portals: apply an entry's max_response_mb when auditing a board (#6510) (05480a6)
- audit-portals: exit 1 instead of aborting on Windows after the audit (#6543) (7de768e)
- avature: parse article--result cards without leading article class (#6593) (8e8a7c3)
- batch-evaluate-gemini: read --limit N and --help instead of running every offer (#6333) (b5de065)
- batch: read batch-state.tsv from the checkout in merge-tracker and reserve-report-num (#6449) (beded4a)
- batch: serialize sync allocation across input aliases (#5512) (116faed)
- batch: take the state lock with flock where mkdir is not atomic (#6545) (6de681c), closes #6362
- browser-extract: refuse a Cloudflare challenge page instead of emitting it as the JD (#6798) (48212e1)
- check-jd-archive: reject batch-prompt's unfilled JD placeholder (#6490) (57e339b)
- ci: keep signature-ci's failure reason on one line (#6714) (221714b)
- close: read a close day whose offset has no colon (#6717) (281f6da)
- community-asks: draw the star ask with emoji that terminals show as emoji (#6741) (98418a5)
- cover: parse generate-cover-letter flags strictly and honor the … (#6384) (7e4a38a)
- cryptocurrencyjobs: stop passing the feed's teaser as the job description (#6746) (5b242a9)
- cv-visual: retry the snapshot apt install, not only the update (#6556) (6c2ac63)
- cv: bind structured facts to source records (#6016) (1aeab42)
- cv: fail the build on any unresolved {{token}} (#6408) (49ae767)
- dashboard, cover: treat an over-budget render as not delivered (#6656) (af8fa29)
- dedup: keep one row for one posting (#6602) (a0e4f0a)
- detect-reposts: fold regional subdomains only on known sites and Greenhouse aliases to one source (#6190) (d59f0d1)
- discover-ats: exit 0 instead of aborting on Windows after the probes (#6687) (01f8367)
- docs: list every routed command in the published menus (#6048) (63154d4)
- doctor: report missing packages before loading them (#5561) (a6cac56)
- eval: archive source JDs and fail unsuccessful saves (#3951) (b4ce1d6)
- eval: include the profile and send each JD only once (#3950) (024b626)
- eval: read .env from the data root in the model runners (#6659) (8bcee77)
- eval: reject OLLAMA_NUM_CTX and *_TIMEOUT_MS values that are not whole numbers in range (#6662) (b4b77ec)
- eval: reserve report IDs against the configured data root (#3948) (93e6bf8)
- fetch-jd: exit 1 on a miss instead of aborting on Windows (#6462) (744666a)
- gemini-eval: load each market's own evaluation file (#6407) (eb86aba)
- getro: warn when getro_max_pages cuts a board short (#6433) (382c29a)
- greenhouse: fetch bodies for title-matched postings on an oversized board (#6673) (1120279)
- http: do not retry a request that already spent its timeout (#6132) (92c1d88)
- i18n: align ru practice retracted-claims definition with en (#6617) (25ec103)
- i18n: layer localized modes on the English canon for scalar modes_dir (#6728) (b124a5a)
- initialize reverse scan pipeline under its shared lock (#6022) (b13837a)
- interview-redflag: skip practice sessions when scoring a company (#6715) (89d70f5)
- interview: check --dealbreaker for delimiters before debrief Step 10 runs (#6541) (#6590) (45d43c4)
- interview: escape backslashes before inner double quotes in stated_priorities.quote rule (#6588) (51bdb79)
- interview: map the contact type to a value debrief-sync accepts (#6548) (4be763f)
- interview: record each confirmed dealbreaker in debrief Step 10 (#6542) (#6591) (8b92efb)
- jd-capture: give company and role names with no ASCII letters a non-empty capture slug (#5689) (af4b126)
- jd-skill-gap: read a markdown link's text, not its target, as CV evidence (#6663) (8d6298a)
- jd-years: stop a list item reading as a required heading (#6463) (05ceb99)
- jibeapply: accurate truncation warning at the page cap, opt-in page_size (#6692) (32b1bec)
- jobspresso, wp-job-manager: pass the full post through so the scan filters can read it (#6585) (ba508d9)
- keyword-match: read a markdown CV's link text, not its targets (#6664) (6babdcc)
- linkedin: keep a recognized place on an imported alert (#6775) (adf8ba4)
- liveness: read Eightfold's "job you are looking for may have closed" as expired (#6670) (f03cd3f)
- liveness: stop a Norwegian "Sorter etter søknadsfrist" control hiding a closure banner (#6788) (#6810) (7a377a4)
- liveness: treat a Paylocity JobNotFound URL as expired (#6599) (64ea4e3)
- merge-tracker: keep the employer URL when an aggregator re-list (#6657) (ed3f2d1), closes #6621
- merge-tracker: refuse headerless TSV additions by default (#6062) (64f6c53)
- modes: score ofertas, training and project against the user's profile (#6283) (819e9df)
- mokahr: read the tenants the old robots.txt rule skipped (#6694) (#6726) (0b958b1)
- name a missing data folder and fail unfinished doctor setup (#6618) (46f375f)
- nodesk: decode the twice-escaped ampersand in titles (#6584) (996c356)
- oferta: show an unverified red-flag file as not evaluated (#6787) (09b8ab8)
- openai-eval: cap the completion with max_tokens to stop OpenRouter 402s (#6676) (5d47f94)
- openai-tailor: cap the completion with max_tokens, as openai-eval does (#6762) (5e7d26c)
- openai-tailor: send OpenCode Go the session id and user agent it asks for (#6800) (594eaf9)
- openrouter-runner: exit 1 when a command fails or is mistyped (#6660) (aacdf09)
- openrouter-runner: wait for the job description instead of a fixed 2 s (#5809) (#6169) (74618c2)
- oraclecloud: warn when max_pages truncates a board; allow opt-in up to 100 pages (#6690) (0f3c7ae)
- outcome: find a posting capture under the slug archive-posting wrote (#6658) (0e15bac)
- pdf: keep a warn-only page-budget overflow off the output path and out of the manifest (#6458) (c48d13e)
- platsbanken: mark a cut board truncated, not just warn on stderr (#6804) (d5dd137)
- plugins: apply the fetch timeout to the response body (#6473) (afc099a)
- plugins: exit non-zero when a plugin run fails (#6125) (a77608b)
- plugins: import the template smoke test's entry by file URL (#6811) (#6812) (d6d17fa)
- portals: repoint Runway to its Ashby board and enable it (#6505) (ea0bb9c)
- portals: report why a board was not verified (#6085) (cc6f0a1)
- preserve fetch method receiver when applying response cap (#6801) (65d9496)
- protected-grounds: correct the DE row's BAG case number (#6748) (a50c74e)
- providers/ibm: reject impossible dcdate like 2026-02-30 (#6802) (4deaa21)
- providers: pass Arbeitnow description through so filters can see it (#6439) (798d956)
- providers: propagate careers_url lang into ADP Workforce Now API calls (#6679) (c2d335b)
- providers: read boards over the 10 MB body cap (#6560) (447ca06)
- providers: read CAREER_OPS_HTTP_TIMEOUT_MS on each request so it works from .env (#6580) (0d82fa2)
- providers: read jobspresso's query-free feed path, robots.txt disallows /*? (#6546) (de6048f), closes #5774
- providers: send Computrabajo's newest-first sort on every path (#6694) (#6739) (e641424)
- providers: walk the whole Jobicy feed; add geo/industry/tag filters (#6612) (90d0cde)
- readme-badges: answer --help instead of treating it as a badge name (#6702) (4e5dd7f)
- readme: serve the Hired Wall, Discord and npm badges from the repo (#6701) (7c08d5d)
- registry: report a pin after its latest release as ahead, not stale (#6786) (57f443b)
- remoteok: pass the description through so the scan filters can read it (#6565) (5bbd05d)
- remotive: pass the description through so the scan filters can read it (#6566) (ced1bb4)
- repo: catch CRLF blobs and unportable paths, ignore exported reports and CVs (#6578) (9c43348)
- role-clarity: read a "Reports to: X" label line as the reporting line (#6576) (70bc932)
- scan-hn: fetch Hacker News through the shared HTTP context (#6680) (fd26764)
- scan: a blocked Georgian city loses to the Georgia state name (#6533) (0aa6518)
- scan: a portals.yml entry can raise its request timeout with timeout_ms (#6582) (07db6e3)
- scan: accept the documented --throttle=<ms> form (#6491) (5008e07)
- scan: accept worldwide labels in country-only filters (#6553) (d58bb54)
- scan: apply scan_history.recheck_after_days in the admission gate and sibling scanners (#5905) (a38e953)
- scan: default to 10 concurrent companies and make both scan limits configurable (#6573) (2cda411)
- scan: gate conditional workplace geography before Inbox admission (#6484) (14bfd95)
- scan: honor employer location decisions for board copies (#6485) (0d585b2)
- scan: keep each allowed city when a workplace policy is set (#6784) (f4ebc45)
- scan: match uppercase ISO codes in location lists case-sensitively (#6616) (4c413c2)
- scan: name companies no provider claims (#6088) (cff21dd)
- scan: read the CryptocurrencyJobs teaser's eligibility clause (#6765) (d62f7d5)
- skill: drop the undocumented user_invocable key from the router frontmatter (#6695) (28123e1)
- stamp the local day, not UTC, in four more writers (#6564) (6882cd6)
- startup-jobs: stop passing the feed's teaser as the job description (#6774) (1374f8a)
- status: map Lapsed to Discarded (#6598) (3031470)
- telegram-channel: read Habr Career links and positional employers (#6363) (9486e6a)
- tracker: recognize the French, Italian and Turkish headers modes ship (#6360) (9b2ba6a)
- tracker: stop a placeholder or a URL spelling hiding which posting a row is (#6390) (abe49bb)
- updater: check --channel main falls back to git before reporting offline (#2550) (738d45c)
- verify-ats: decode every entity in the text keywords are searched in (#6464) (b34707a)
- verify-ats: recognize Norwegian Bokmål CV headings (#6654) (43eaaf5), closes #6615
- verify-ats: recognize Vietnamese CV section headings (#6652) (8ce89bd)
- verify-cv-facts: read link text, not link targets, when matching a claim to its source (#6606) (6a6884b)
- verify-cv-facts: run scope-verb and adoption checks on Simplified Chinese CVs (#6730) (f0e546e)
- verify-pipeline: detect corporate-form duplicate companies (#6528) (1350f9d)
- verify-pipeline: don't flag a re-evaluation one tracker row keeps on record (#5985) (abefa6d)
- verify-portals: check a board with its entry's timeout_ms and max_response_mb (#6718) (64e63d5)
- web: rank the week's matches before paging them (#6783) (d5b579a)
- web: read script-rendered JDs before the evaluate worker starts (#6604) (e56ab08)
- web: show the real PDF render failure, not the warnings printed before it (#6624) (d8f0ab5)
- weworkremotely: pass the description through so the scan filters can read it (#6568) (b03c44b)
- workable: keep telecommuting and a state-only place (#6140) (eeead8e)
- workday: ignore a slice's own applied facets when reading the true board size (#6672) (127b450)
- workingnomads: pass the description through so the scan filters can read it (#6567) (ea02d07)
Performance Improvements
- archive-posting: load playwright only when saving (#6134) (f3f25dc)
- img-to-pdf: load playwright only when rendering (#6135) (9711088)
- scan: fetch up to 32 companies, 10 per host (#6133) (975abe0)
Sponsors
SerpApi sponsors career-ops through its fiscal host, Open Source Collective.