v1.4.0

ai-job-search-dev/ai-job-searchv1.4.0Aug 7, 2026by MadsLorentzen

Upgrading?

AI Summary

Fixes silent failures in the application workflow and fetch logic. Applications are now tracked, and fetches respect robots.txt.

Key Highlights

  • /apply now writes a drafted status so applications aren't lost.
  • /rank no longer marks live jobs as expired due to 403s (now respects robots.txt).
  • All portal CLIs now use honest User-Agents.
  • Danish demo portals are disabled by default.

Breaking Changes

  • drafted is now a legal tracker status (consumers must handle it).
  • tools/robots_check.py gates retries on robots.txt (sites denied are never overridden).

New Features

  • /setup captures a structured Languages table.
  • --jobage-minutes flag for LinkedIn.
  • .env file is now gitignored.

Full Release Notes

## Why update

Two silent failures in the flagship flow are fixed:

**Your applications no longer vanish.** `/apply` wrote your CV and cover letter to disk and then recorded nothing - a drafted-and-submitted application was invisible to `/gmail-sync`, `/html-report`, `/notion-sync`, `/interview`, and `/rank`'s dedup, and nothing ever detected the loss (#269, fixed in #291 by @jakob1379). `/apply` now writes a `drafted` row to the tracker, a new `drafted` status flows through every reader honestly (unsent drafts are never counted as "waiting for a reply", never chased with follow-ups, and never reported as submitted), and an employer's acknowledgement email lets `/gmail-sync` promote the row to `applied` with the real date - catching exactly the case where you submitted by hand and forgot to run `/outcome`.

**Live postings are no longer declared dead.** `WebFetch` identifies itself as a bot, and many corporate sites answer it with HTTP 403 while serving browsers normally. `/rank` read that as "posting gone" and marked live jobs `expired`; `/apply` silently fell back to search-snippet prose (#277, reported and fixed by @kblackma). The new `09-web-research.md` methodology file defines the trust boundary and a retry escalation - and the retry is **gated on robots.txt** via the new `tools/robots_check.py`: a site whose published policy permits access gets a browser-header retry past its WAF; a site that has said no is never overridden. The gate itself was then adversarially hardened (#286): a soft-200 HTML page no longer reads as permission, and percent-encoded Disallow rules now match.

## Honest self-identification, completed

Every shipped portal CLI now sends an identifying `User-Agent` token instead of a browser spoof: jobnet and jobdanmark (#283 by @oscarbol09), jobbank and the jobdanmark detail path, and finally linkedin-search - the last holdout. Live-verified on every portal: identical responses under the honest token. Sites can now see exactly who is asking, which is the posture the robots gate depends on.

## Also in this release

- **Danish demo portals ship disabled** (#288, suggested by @a-vysokov) - `/setup` enables them when your market is Denmark; everyone else's `/scrape` stops spending tokens on Danish boards it never needed.
- **`--jobage-minutes` on linkedin-search** (#302 by @kgurnoor) - sub-day freshness windows for early-applicant timing; LinkedIn filters at second granularity server-side.
- **`.env` can no longer be committed** (#303 by @Haseeb-1698) - ignored and pinned in the security guard, protecting forks whose generated portal skills use an API token.
- **Negative and fractional filter flags rejected** in the Danish portal CLIs (#281 by @oscarbol09) - some portals silently ignore invalid filters and return unfiltered results.
- **The upstream checker no longer prints a false all-clear** when upstream renames or deletes a tracked file (#282 by @oscarbol09), and it now tracks `09-web-research.md`.
- **Spec-pinning tests** for the Language Gate's `/rank` contract (#278 by @GabrielMensi).
- **A video walkthrough** in the README's Quick start - The Next New Thing's hands-on tour of the workflow, for newcomers who want to see it before reading.

## Fork reconciliation

`python3 tools/check_upstream_updates.py` will flag three methodology files this release:

| File | Version | What changed |
|---|---|---|
| `job-application-assistant/SKILL.md` | 1.1.0 → 1.3.0 | Bash added to allowed-tools (robots-gated retry); new Step 3b records the application |
| `03-writing-style.md` | 1.1.0 → 1.2.0 | Rule 5 routes 403s through the web-research escalation |
| `09-web-research.md` | new, 1.1.0 | New file - adopt as-is unless you have your own fetch conventions |

Two personalized-file landmines to check by hand:

- **Danish-market forks:** the four Danish portal `SKILL.md` files now ship `enabled: false`. If you search the Danish market, flip them back to `enabled: true` after updating (or re-run `/setup --section search`). Forks that already curated their portal set are unaffected.
- **Tracker vocabulary:** `drafted` is now a legal tracker status. If your fork customized `/outcome`, `/gmail-sync`, `/html-report`, or `/notion-sync`, reconcile the new status handling - unsent drafts must stay out of funnel statistics and follow-up nudges. (A full single-definition cleanup of the status enum is in review as #304's sibling effort, #298/#299 - it will land in a future release.)

## Credits

@jakob1379 (the #269 diagnosis and the #291 fix, plus the #298 follow-up analysis), @kblackma (the 403 investigation, `robots_check.py`, and the adversarial hardening in #286), @oscarbol09 (#281, #282, #283), @GabrielMensi (#278), @kgurnoor (#302), @Haseeb-1698 (#303), @a-vysokov (#288).

Related Repositories