github career-ops-hq/career-ops career-ops-v1.37.0
career-ops v1.37.0: Worth Your Time

latest release: web-v0.15.0
5 hours ago

This release is about knowing what is still worth your time: postings you are tracking get checked for you, and every report opens with its verdict. The dashboard work (interview prep, cover-letter drafts, row comparison) ships in web-v0.15.0.

Highlights

  • Is it still open? check-liveness.mjs --tracker checks the postings behind your open tracker rows, and --discard-expired moves the Evaluated ones that closed to Discarded when you add --apply (it is a dry run otherwise), by @mbrow190 (#5177, #5241). When you find a posting by hand that the scan missed, coverage-check explains why, by @IaroslavMazur (#4849).
  • Reports that lead with the call. Every report now opens with a Verdict block, by @rubicon (#3960). Block G gains Signal 18, recruiter identity verification, by @stdOWL (#5080), and the fact gate reads German count claims, by @duathron (#5074). Signals 8 to 17 now load only when their trigger holds, so evaluations read a shorter prompt, by @mbrow190 (#5229).
  • More places to look, in your language. A Google provider that reads the first results page only, as robots.txt allows, and an opt-in Ashby embed source, by @kellymmcgrath (#4078, #4298). Kalibrr and Tech in Asia for Southeast Asia, by @rafifshaf-fun (#4420). Platsbanken, Sweden's national job board, by @gdut4140 (#5069). preferred_languages keeps the version of a posting in the language you apply in, by @ilyakanevskiy (#5285, with #4789, #5044, #5260, #5264).
  • Your setup. Follow-ups can be turned off, by @mbrow190 (#5164). An opt-in local LaTeX installer so the LaTeX path compiles out of the box, by @CaptainCo0L (#5133). The Nix dev shell works on a fresh NixOS clone, by @asinglesprinkle (#5073).

Security

  • providers: a literal private IP host (such as http://127.0.0.1/) is now refused on the direct path too, not only behind a proxy (GHSA-jh4g-g3vm-m943). Reported by @gtyree, who also stopped the full ATS sweep from piling up thousands of idle connections in this release (#4841).

Updating

Your agent offers this update at the start of your next session. If it doesn't, run this once in your career-ops folder:

node update-system.mjs apply --confirm

Your CV, profile, tracker and reports are not touched.

Thank you

This release carries 263 pull requests from 61 people, 22 of them contributing for the first time.

1.37.0 (2026-10-15)

Features

  • add Assessment lifecycle and follow-up status controls (#4317) (8cb0161)
  • add strong local listing identity schema (#4642) (b19d16c)
  • coverage-check: explain why the scanners missed a posting found by hand (#4849) (ebcf15a)
  • cv: render the build payload to markdown with --markdown (#4868) (2dc4837)
  • facts: read German count claims in the fact gate (#5074) (e8e7794)
  • followups: let users turn off follow-ups (#5164) (0a388c8)
  • latex: opt-in local LaTeX engine installer so the latex path compiles out of the box (#5133) (0137039)
  • liveness: check the postings of open tracker rows (#5177) (93a9f47)
  • liveness: discard expired Evaluated tracker rows (#5241) (c510bad)
  • nix: make the flake dev shell work on a fresh NixOS clone (#5073) (389995f)
  • oferta: add Block G Signal 18, recruiter identity verification (#5080) (0ab7352)
  • oferta: add the Verdict (lead) block the report contract has been missing (#3960) (a2213ab)
  • plugins: expose aggregatorPostingId through ctx (#5070) (e5adfad)
  • plugins: register read-only workspace MCP plugin (#5102) (9d711dc)
  • portals: Latin America and cybersecurity sections in the example (#4858) (93d4e52)
  • providers: add google (first results page only, per robots.txt) (#4078) (c7f70e5)
  • providers: add Kalibrr and Tech in Asia providers for SEA (#4420) (13d0e0c)
  • providers: add platsbanken — Sweden's national job board (JobTech Dev API) (#5069) (f170088)
  • providers: allow CAREER_OPS_HTTP_TIMEOUT_MS to raise the fetch budget (#4187) (868a2a5)
  • providers: ashby opt-in embed source for boards with the API disabled (#4298) (2310b0e)
  • providers: Personio language versions from the careers page (#5044) (c7e5ba5)
  • providers: recruitee reads requirements, publish date and salary (#5260) (76c53a0)
  • providers: recruitee returns the language versions of its offers (#5264) (a0317de)
  • reply-watch: review local status proposals (#4645) (f9ae4a0)
  • router: rename the session after the posting once its JD is read (#5303) (#5305) (4647bcf)
  • scan: language-aware URL dedup for language versions served at one URL (#4789) (66314d3)
  • scan: preferred_languages picks the language version of a posting the scan keeps (#5285) (ff09231)
  • test-all: run discovered suites in parallel with --jobs N (#5076) (#5077) (0026e40)
  • test-all: run the inline sections alongside the discovered suites (#5146) (#5150) (9a2f8d2)
  • upskill: show the requirements behind your recent skips (#5242) (f541063)
  • web: add the cv studio edit model and api for tailored cvs (#5232) (1fcd8d1)
  • web: ATS keyword coverage on the report view (#4579) (886b834)
  • web: company evidence card on the report (and fix the data root it reads from) (#4581) (bcfdcdc)
  • web: expand Analytics into progress and search stats (#4310) (6e0cd23)
  • web: recurring skill gaps on the CV page (#4583) (85fdac2)
  • web: show the posting url in the tracker view (#5130) (d79024c)

Bug Fixes

  • analytics: count only in-flight rows as active applications (#5186) (ed10c7a)
  • answers: preserve subsequent Markdown sections on update (#5036) (3b900f9)
  • archive: recognize tracker-linked PDF and text captures (#5041) (0376995)
  • batch-runner: stop when the report number reservation fails instead of launching a worker without one (#5054) (e978e0b)
  • batch: detect worker reports without compgen (#5068) (e84972f)
  • batch: don't treat the report reservation sentinel as a worker artifact on an agency hold (#4934) (7c129af), closes #4933
  • batch: give node a Windows path for PROJECT_DIR under Git Bash (#5051) (83fe8be), closes #4910
  • batch: try fetch-jd.mjs before WebFetch when the prefetched JD file is empty (#4246) (2f1a548)
  • cli: --help was discarded, so the script ran instead (#3998) (f3357ef)
  • cli-flags: reject empty value in --flag= form when requireOperand is set (#5127) (#5255) (460a0ea)
  • cli: handle help before exporting ATS text (#5046) (f28cd70)
  • cli: reject recognized short flags as missing operands (#5086) (b5d94b0)
  • cli: tracker.mjs delete runs for real when --dry-run is mistyped: validate each subcommand's flags (#4630) (03e9f5a)
  • cli: validate scan migration flags before accessing history (#5047) (2cae1cb)
  • cli: validate-portals.mjs rejects a mistyped flag and gains --help (#4601) (#4874) (036df14)
  • company-funded: discoverFundedCompanies takes opts.now, so its test no longer depends on today's date (#5302) (81f4c4e)
  • consider: run the CSRF handshake through ctx.fetchResponse (#4925) (9b8e92d)
  • contacts: anchor the --vcf write guard to the data root, not the script directory (#5265) (38af581)
  • cv-facts: check the tools a dot, a second trigger, or a connector hid from the gate (#5011) (87f8771)
  • cv-markdown: boldLineText rejects a line holding several bold runs (#5267) (ac2a9ae)
  • cv: drop the summary block when the summary is empty (#5021) (8b5d677)
  • dashboard: 0.0% rates in red before the user has applied to anything (#4941) (fe143cb), closes #4940
  • dashboard: a relative CAREER_OPS_ROOT resolved against cwd, so the dashboard could read a different tracker than the scripts write (#4956) (bd4ed43), closes #4955
  • dashboard: an unreadable tracker was reported as a missing one, and broke the screen silently on reload (#4939) (5a24ce5), closes #4937
  • dashboard: exclude cover letters from CV manifest lookups (#4975) (8879652)
  • dashboard: read batch-input.tsv and batch-state.tsv from the checkout (#5254) (878228e)
  • dashboard: resolve every states.yml spelling the way Node does (#4926) (1416bf5)
  • dashboard: resolve PDF index from the tracker workspace (#4974) (73f38d6)
  • dashboard: run PDF generation from the code checkout (#4973) (1db6517)
  • dashboard: the language key cannot reach Spanish, and strands a Spanish user in English (#4944) (163a5ee), closes #4943
  • data-root: keep the code root and the data root apart in openrouter-runner and batch-runner (#5116) (0d67933), closes #5115
  • dedup-tracker: keep rows whose requisition ids differ (#4985) (ce9da50)
  • dedup-tracker: rank every status through states.yml and stop when it cannot be read (#5108) (898c1ec)
  • dedup-tracker: rank status aliases like their canonical state so an Applied-or-later row is never removed (#4986) (ec288ad)
  • dedup: keep the removed repost's report link in the keeper notes (#5185) (ffb9b60)
  • discover-ats: keep portals.yml valid when --write adds companies (#5053) (f462465)
  • doctor: detect the active CLI and user-level MCP config for OpenCode (#5105) (9695a8d)
  • doctor: look for portals and pipeline where the scanner reads them (#5166) (42f42a8)
  • doctor: report a title_filter positive its own negatives veto (#4882) (2532155), closes #4873
  • doctor: say setup is not finished when user files are missing (#5170) (0ece057)
  • doctor: warn when a CLI skill entrypoint is a symlink-target stub (#4589) (#4734) (ae72f5b)
  • eval: preserve candidate source-of-truth rules during compression (#5033) (d0b73f2)
  • extract: read the content root with the most rendered text, not the first (#4905) (b54c484)
  • fact-gate: extend German count nouns, pin remaining coverage gap (#5125) (#5148) (6825c88)
  • followup-seed: bind the JSON error contract to makeCliFailWith (#5225) (1ac4e97)
  • followups: canonicalize follow-up lock paths (#5002) (e58dff2)
  • followups: honor legacy history when seeding (#5003) (b30abd7)
  • followups: reject malformed cadence integers (#4971) (2cebe96)
  • followups: treat an empty tracker as a first run, not a failure (#5168) (c2abeec)
  • generate-pdf: take the pipeline lock and write atomically when updating pdf-index.tsv (#4987) (3d8fae9)
  • github: point CODEOWNERS and copilot-instructions at paths that exist (#5061) (1407b74), closes #5058
  • gitignore: keep nested user data ignored (#4945) (1bcfeb5)
  • gmail: trust only Gmail's own DMARC verdict, decode quoted-printable parts (#3873) (080a236), closes #3731
  • invites: match complete requisition IDs (#4976) (af9897b)
  • jd-skill-gap: keep multi-word skills whole and flag unread Chinese requirements (#5294) (1edbbec), closes #5292
  • jd-skill-gap: match Simplified Chinese requirement headings (#5032) (a600dc9)
  • keyword-match: score the archived JD when a report has no keyword section (#5282) (9e01ac6)
  • liveness-api: resolve Ashby postings whose org slug contains a space (#5020) (84eceef), closes #5018
  • liveness: a DNS failure on the posting's own host no longer drops it for good (#5075) (b7d9fbe)
  • liveness: a failed browser launch no longer ends the batch (#5111) (6b51ed4)
  • liveness: a failed page load no longer fails every check after it (#5299) (15a13bf)
  • liveness: a page still empty when the poll ends is uncertain, not expired (#4927) (3613707)
  • liveness: a tracker blocked by a DNS filter no longer drops the posting (#5015) (64e7b3b)
  • liveness: add the Workable API rung (#5110) (5dd1e1a)
  • liveness: expire cached DNS failures (#4972) (e48dabd)
  • liveness: keep npm packages out of what the web loads from liveness-browser (#5251) (d2ab42c)
  • liveness: read custom-element buttons, so live UltiPro postings are not dropped (#4928) (8ef009b)
  • liveness: read Dutch Apply buttons, so live VDAB and Recruitee postings are not dropped (#5244) (6caf821)
  • liveness: read the 'can't view this job' closure sentence as expired (#5112) (753bd08)
  • modes: select the row by report id when marking an application applied (#5204) (5397072)
  • openai-eval: pass modes/_profile.md to buildBudgetedPrompt (#5202) (7e3eabb)
  • openrouter: reset a model's 429 count when it answers, so only consecutive 429s blacklist it (#4913) (e46e656)
  • paste-reply: serialize concurrent appends so no reply candidate is dropped (#4922) (34beb13)
  • pdf: keep the catalan l·l intact when normalizing cv text (#5165) (ce2d0f3)
  • pdf: pass the image decode timeout as options (#4977) (441c652)
  • pdf: read a local candidate.photo path only when config/profile.yml names it (#5094) (#5143) (88d6e2f)
  • pdf: run the fact gate on every --batch manifest entry (#5060) (e90f50b), closes #5057
  • plugins: avoid duplicate localhost in capability card (#4997) (ae21138)
  • plugins: collapse one posting's urls at ingest with postingKey (#5236) (ebf36d0)
  • plugins: explain a drifted local plugin in the tamper warning (#5027) (#5028) (04d3afc)
  • plugins: pass the data root to plugins through ctx, so gmail and apify write under it (#5139) (d4cbcc3), closes #5138
  • portals: point moved example boards at their new ATS and disable dead ones (#5224) (6caec73)
  • providers/lever: filter on the full posting text, not the intro (#4932) (7e0a3d0)
  • providers: raise zh-CN page caps to 100 and warn on truncation (#5246) (db0410d)
  • providers: retire torre, search API rejects every request with HTTP 400 (#4871) (9570926)
  • rank: bind batch scores to original row occurrences (#4970) (2bbd662)
  • rank: forward model selection to Codex and OpenCode (#4969) (bff0474)
  • rank: prefer the CAREER_OPS_CLI agent over auto-detection (#5226) (5af4f57)
  • rank: show why the cli failed without echoing the prompt (#5175) (60ee16f)
  • reconcile: read batch state from the code checkout (#5005) (ccd864b)
  • reply-watch: bind legacy review consent to unchanged tracker identities (#5034) (3460748)
  • repo: renormalize tests/upskill-known-skills.test.mjs to LF (#5301) (ab47db6)
  • reserve-report-num: reject unrecognized arguments instead of reserving a number (#4914) (2e2db29)
  • role-matcher: keep sibling requisitions apart when each title has a content word the other lacks (#5266) (11d2c1c)
  • scaffolder: the usage text names the published package (#4938) (c40cfd0)
  • scan-ats-full: a later-page iCIMS failure is not a dead-board miss (#5045) (fec9e6c)
  • scan-ats-full: refuse --resume when a portals.yml filter changed since the checkpoint (#5084) (0b7e8bc)
  • scan-ats-full: release each board's connections in per-tenant-host stages (#4841) (b03db0a)
  • scan-hn: fail on an unparseable portals.yml instead of scanning with the default keyword (#4921) (#4923) (e44aae6)
  • scan-hn: look postings up in the seen set by their normalized URL (#5324) (6205e85)
  • scan: collapse whitespace before matching title keywords (#5161) (a7fcd6d)
  • scan: reject softgarden and csb dates past the end of the month (#5206) (5fe1c8a)
  • scan: stop reading a trailing country code as a us state (#5159) (fa5d18f)
  • scan: strip trailing gender tags from the role dedup key (#5190) (9e0cbb1)
  • set-status: anchor the first follow-up on the apply day, not the evaluation date (#5192) (08ad070)
  • skill-gap: read a requirements list written on the header line (#5173) (b5a3a47)
  • skill-gap: read Competencies headings as a skills section (#5281) (4c27d89)
  • skill-gap: read requirements copied without list markers (#5280) (aefe68c)
  • smartrecruiters: keep special characters inside fallback posting IDs (#5321) (31daebb)
  • stats: read transition history beside the selected tracker (#5035) (05cc706)
  • successfactors: page csb boards by date and resweep when short (#5189) (44b5a50)
  • successfactors: read csb posting dates in the query locale order (#5191) (5ce8b74)
  • sync-pdf-flags: set the PDF flag only while the CV PDF is on disk (#4777) (#4565) (11ad8db)
  • test-harness: link the dependency tree a worktree inherits, in both suites (#5055) (b528fe5)
  • test-harness: run the suite against a scratch data root and guard the real one (#5137) (2bebe32)
  • tests: grade tracked CLIs only in help-flag-handled, not plugins.local/ (#5307) (#5308) (68ab94f)
  • tracker: a language param on one URL is not proof of two postings (#5283) (cd2e05b)
  • tracker: delete backs the tracker up before removing a row (#5066) (3ef8930), closes #5064
  • tracker: keep the pdf check when a re-eval's prior pdf is still on disk (#5128) (d204b1e)
  • tracker: read a decimal-comma score instead of truncating it (#5199) (bd4fa7c)
  • tracker: read decimal-comma scores in merge and dedup (#5208) (6e47ae1)
  • tracker: resolve status aliases through states.yml in analyze-patterns and tracker-sync-check (#5065) (1ad52da), closes #5063
  • update-system: make apply() verify the target it is about to install (#3805) (74c6226)
  • update-system: read the profile directly when a cv-templates import is missing (#5043) (f4840b7)
  • update-system: rollback reads the failed update's own target manifest (#3782) (9ce2bcd)
  • updater: skip unpaired backups on rollback (#5135) (#5256) (77027cc)
  • updater: walk REEXEC_FALLBACK_FILES' own imports in the self-reexec checkout (#5001) (4b81bc5), closes #4999
  • validate-portals: accept title_filter_full keys in by_title_keyword (#4931) (5e96f22)
  • verify-ats: read a white declaration with CSS whitespace (#4924) (0709815)
  • verify-cv-facts: check tool names after a leading "a" or "an" (#5085) (2cda069), closes #5023
  • verify-cv-facts: normalize tool fragments instead of dropping them (#5030) (5f17799)
  • verify-cv-facts: treat a bare "building" as prose, pin the #4394 limits (#4395) (4f5df07)
  • web: deny Write/Bash when evaluating untrusted JDs (#3629) (a98717e)
  • web: preserve company evidence scope and source certainty (#5007) (33c927c)
  • web: reject private URLs in Apply session (#3627) (3444fdd)

Performance Improvements

  • batch: run 3 claude workers by default and drop to 1 after a rate limit (#5237) (444ba28)
  • modes: load block g signals 8 to 17 only when their trigger holds (#5229) (915ee56)
  • scan-ats-full: rule Greenhouse boards out from a body-less listing first (#4850) (3985818)
  • scan: fetch feishu job pages four at a time (#5174) (e4fae2c)
  • test-all: check syntax in one process instead of one per file (#5078) (#5147) (206e5f2)

Sponsors

SerpApi sponsors career-ops through its fiscal host, Open Source Collective.

Don't miss a new career-ops release

NewReleases is sending notifications on new releases.