Rows with a placeholder company name (Stealth, Stealth Startup, Confidential, Startup, Remote and similar) have no company key, so they are now delivered free of charge like rows without a name, and all seven history fields, monthsChecked included, are null. Before, they were billed with monthsChecked 13 and six null fields. The free-row count in the run status follows the same rule (7 of 3611 rows in 12 live threads were affected).
Maximum posts: threads are read only until enough rows are ready, so asking for 12 months with the default 50 posts reads one thread and one history window instead of 23 threads (34.5 s before, about 18 s now). The run status names the threads that were read and lists the ones skipped at the limit; its counters (header, salary, filtered out) now describe only the threads read. Validation of every thread that will be read still happens before the first row is delivered.
Only new since last run: the memory now stores every post of this run's dataset, including posts delivered before a migration restart, so the next scheduled run no longer returns them again (measured: 15 of 30 rows were repeated and billed again).
History window line in the status no longer says "X of X explained"; it reports the window of the threads read, months read and months with no Hacker News thread.
Tests: the Retry-After budget test uses a frozen clock (it failed in the full suite when a run took 1 ms), maximum posts over several threads, placeholder company, restart memory with a restored emitter state.
Removed an unused export (statusRow).
0.6
Run time limit: one budget of 75 s for the whole run now counts the pauses between attempts and the time spent in failed attempts, every attempt is cut off by a hard deadline of 25 s (Promise.race with an abort signal, also when a proxy accepts CONNECT and stays silent, where the got-scraping timeout never fired) and no attempt starts with less than 3 s left. Measured on a local server that accepts the connection and does not answer: one task used to take 250.7 s of real time with 10.5 s counted against the budget; the 14 tasks of a prefill-shaped run took about 37 min at the default timeout. Now the same source ends in about 75 s and later tasks fail at once without a request. Successful responses, however slow, do not use the budget. Request timeout 60 s lowered to 25 s.
companyKey is now the normalised company name only (yardstik, factoryai), never the domain: before it switched between yardstik.com and yardstik depending on whether the post linked the website (10 of 496 names in three live threads, all differing between months). The domain is still used inside the run to match the same company across months, so monthsHiring and the other history fields are unchanged. The field description no longer promises stability across different spellings of one name.
postsThisMonth description names its null (history off, no company key), and so do the other history fields.
Place rule: a name with a place after a comma is a place only when the part before the comma is one word, or up to three words with a state after the comma (Troon, Scotland, Santa Cruz, CA); Paul Scherrer Institut (PSI), Switzerland, adapptiv Labs, Switzerland, LiquidFi (Miami, FL) and My Signal Well (Austin, TX) are companies again. Parentheses are ignored in that rule.
Roles: a place or a work mode is no longer taken for a role (Findigs | NYC gives no role and location NYC; √ REMOTE (US) is a work mode).
README: seconds per earlier thread (2 s measured), budget wording.
Tests: deadline on a silent source on a virtual clock (14 prefill-shaped tasks, single task, limit of one attempt), a send that ignores its own timeout and a real TCP server that never answers, companyKey with and without a link, place and role cases.
0.5
Company key: a domain becomes part of the key only when its name matches the company name (factory.ai for Factory AI, klara.com for Klara Systems), and the key is the registrable domain (docs.sumble.com gives sumble.com). Before, the first non-job-board link in the post text was used, so a TechCrunch link made techcrunch.com the key of Sumble (monthsHiring 11, firstSeenMonth 2025-11, isNewCompany false, against 2 months by name) and lnkd.in the key of Flow Traders (7 against 6). Measured on the 13 threads of the default window: 44 domains were the key of more than one company; now 15 domains map to more than one name and all 15 are spellings of one company (Klara Systems / Klara Inc., Factory / Factory AI, Krea / Krea AI). companyWebsite follows the same rule.
Header company: a first part that is a place (Boston, MA, NYC, San Francisco or Portland, OR, Troon, Scotland), a role or employment phrase (Software Engineer, Full-Time Frontend Engineer) is no company; the row is free of charge and has no key. A first part with a -, • or : tail keeps only the name (1aim - Berlin, Germany gives 1aim, Square: Developer Platform Engineers gives Square); Remote (US) Close, *Fastly and Nanocloud Software [ ] are cleaned. Rows with a company name (billed) per thread, before and after: 2011-12 10 and 2, 2012-06 32 and 17, 2014-06 95 and 76, 2016-01 294 and 253, 2026-09 239 and 235; the removed rows are places, roles and employment phrases, and reading the remaining names by hand finds only a few misses (Monroe MI, Stryd, Boulder, CO, Techstars Boulder 2015), because the place list is finite. Names that contain a place word stay companies (Kong, Boston Dynamics, Washington Post).
Waiting budget: all pauses of a run (backoff and Retry-After) draw from one 75 s budget. A retry that would exceed what is left is not made and the request fails with a message naming the budget, so a source that keeps answering 429 can no longer keep the run past the five-minute limit (one task with Retry-After: 120 used to sleep 362 s). Request timeout 90 s lowered to 60 s.
New input proxyConfiguration (off by default) for the case that the Hacker News search API rate-limits the shared platform IP.
Checked live (month 2026-09, 12 months of history, 254 rows): Sumble companyKey sumble, monthsHiring 2, firstSeenMonth 2026-06 (was techcrunch.com, 11, 2025-11); Flow Traders flowtraders, 6 (was lnkd.in, 7); no key is shared by two different names; 19 rows without a company are free of charge (15 before); header coverage 235 of 254; minMonthsHiring: 3 keeps 107 rows (was 113).
Tests: place, role and employment names, names that contain a place word, domain/name agreement, link from the post text, registrable domain, waiting budget with 429/503 codes on a virtual clock (14 tasks, parallel tasks, Retry-After 120 and 60+30), proxy passed to the request.
0.4
A thread with fewer than 20 comments in the listing (a stub next to the real thread, as December 2011: 4 comments by whoishiring against 251 by another account) is looked up by title and replaced by the thread with the most comments. Checked for all 186 months: only 2011-12 is affected; month 2011-12 now returns the full thread instead of one row.
A past thread with fewer than 20 readable posts fails the run as incomplete instead of delivering one row.
Asking for history where it cannot be computed (a thread below 75% header share or fewer than 20 posts in the window, 68 of 186 months with the default 12 months) now fails the run with a message pointing to historyMonths: 0, instead of delivering billed rows with every history field null. With historyMonths: 0 those months return plain posts as before.
Tests: stub thread in the listing for a requested month and for a history month, a past thread that stays thin after the title search, history on an unreadable window with and without filters.
0.3
Withdrawn: the 0.2 claim that Hacker News published no thread for 2011-07, 2015-04 and 2015-05. Those threads exist (283, 949 and 893 comments); the 0.2 listing filtered by one author name and missed them (2015-04 and 2015-05 are posted by the account _whoishiring, 2011-07 by Aloisius). The listing now covers both whoishiring accounts, and every month the listing lacks is looked up by its title with a Hacker News search. A month is a gap only when that search finds nothing; a thread found with fewer than 10 comments, or a failed search, fails the run.
Months 2011-07, 2015-04 and 2015-05 can be requested directly. The error for a missing month no longer states a thread range taken from a truncated listing.
A truncated listing no longer makes history wrong: missing months are looked up one by one, so a listing cut to one month returns the same rows as the full one.
Header coverage is measured per thread. The Company | role | location header settled around 2016 (2012-01 5%, 2013-01 20%, 2014-01 26%, 2015-01 35%, 2016-01 79%, 2022-01 95%). Rows without a company name are no longer billed. Company history is not computed when the requested thread or any thread of its window has a header in under 75% of posts; with Only first-time companies or Minimum months hiring the run stops with an explanation instead of marking companies as new.
A recent thread with posts but no readable header still fails the run.
Checked live on the new Algolia listing (514 hits, 185 months plus 2011-07 by title): 2015-04 and 2011-07 with history off return 400 and 224 rows; 2011-08 with 1 month of history reads 2011-07 and reports history not computed; 2015-06 with 2 months and Only first-time companies stops with the explanation; the latest thread with 12 months of history reads 12 of 12.
Tests: fixtures hold real title-search responses and a real 2011-07 thread; added cases for a truncated listing resolved by title search, a failing title search, a thin thread, a fuzzy search result from another month, a thread outside the author listing, free rows and the 75% header rule. The filter assertion now compares with the unfiltered run.
0.2
History window is now explained month by month. A month missing from a complete Hacker News listing is a real gap (Hacker News published no thread for 2011-07, 2015-04, 2015-05): the run succeeds, the status names the gap and consecutiveMonths skips it.
A listing that stops before the window, a listing with implausibly many missing months, or an HTTP error in the middle of paging now fails the run instead of returning history computed from a few months. This covers onlyNewCompanies and minMonthsHiring, which could previously bill rows for companies that had hired before.
The listing is paged deeper when the newest thread is missing or skipped, so a 24-month request no longer reports a gap that does not exist.
The coverage line counts explained window months instead of posts with a company key.
Checked live: 2011-08 with 1 month of history and 2015-06 with 2 and 6 months (real Hacker News gaps) succeed with the gap named; monthsBack 24 reports 24 of 24 months explained.
README: header and salary rates corrected, new section on the history window; fixtures hold a full 12-month window and a test per case (complete window, real gap, truncated listing, interrupted paging).
0.1
Initial release: monthly "Who is hiring?" posts as structured rows, with company history across earlier threads, salary parsing with yearly equivalents, filters and "only new since last run".
Verified with three inputs (see the smoke report):
Typical: latest thread, 12 months of history, up to 100 posts; every row carries history fields.
Edge: a specific month with history switched off, and filters for technologies, remote work, minimum salary and visa sponsorship.
No results: an unmatched keyword returns one unbilled status row with found: false; a month without a thread fails the run.
Review fixes: when no earlier thread of the history window is in the Hacker News listing the run now fails instead of marking every company as new; gaps inside the window are reported in the run status with the missing months; the coverage line now counts posts compared against loaded earlier threads; the README example no longer shows a found field.