feat(linkedin-search): report closed postings via isActive, wire into /scrape (adopts #280) (#383)

* feat(linkedin-search): add active status verification for job postings

* fix(linkedin-search): scope closed-posting detection to the top card, pin with tests (#280)

The first version matched five markers against the whole document, so
recruiter boilerplate quoting 'no longer accepting applications' in a
description flagged a live job CLOSED. Detection now stops where the
description markup begins and matches only the two markers real closed
pages carry (closed-job__flavor and the banner text, verified against
live guest pages); the three speculative phrases are dropped. Four new
fixture tests pin both directions plus the two description false-positive
cases - the false-positive pair fails on the unscoped version.

* feat(scrape): mark closed-at-source LinkedIn postings expired, never drop (#280)

/scrape Step 2 now consumes linkedin-search detail's isActive: a job whose
posting page renders the closed banner is written to seen_jobs.json with
status expired rather than silently dropped, per the /rank marking pattern -
the fix for the ghost-jobs class in #331. isActive: true is documented as
absence of the banner, not proof the posting is open.

---------

Co-authored-by: Navakanth Reddy Dumpa <navkanthr@gmail.com>
This commit is contained in:
Ayobami Adegoke
2026-08-29 11:12:26 +02:00
committed by GitHub
co-authored by Navakanth Reddy Dumpa
parent 730dcfb079
commit 3d296448bd
5 changed files with 88 additions and 0 deletions
+10
View File
@@ -94,6 +94,16 @@ and URL. For jobs worth a deeper look, fetch full detail with that portal's `det
command (see its SKILL.md — do not guess flags) to extract **key requirements**,
**application deadline**, and a brief description snippet.
**Closed-at-source detection:** `linkedin-search detail` also returns `isActive`.
`false` means the posting page itself renders LinkedIn's "No longer accepting
applications" banner — the job died between being indexed and being fetched (expired
LinkedIn URLs redirect to *similar live jobs*, so a search hit can be a ghost). Mark
such a job, never silently drop it: write its entry to `seen_jobs.json` in Step 4 with
`"status": "expired"` and leave it out of the Step 5 presentation — an absent entry
looks identical to a job never seen, and the recorded status is what makes a later
ghost report self-triaging. `isActive: true` is only the absence of that banner, not
proof the posting is open; deadlines and dead URLs remain `/rank`'s job.
**From WebSearch results:** Use `WebFetch` on the posting URL and extract the same
fields manually. If it returns HTTP 403, retry with browser headers via curl per
`.claude/skills/job-application-assistant/09-web-research.md` before giving up — most