Skip to content

fix(ci): anchor arXiv digest window to announcement bands #79

Description

@Jammy2211

Overview

The #papers digest silently drops strong-lensing papers. Two were missed on 2026-07-15 (2607.12129, a lensed-arc candidate in MACS J0308.9+2645; 2607.12209, the NGC 6505 Einstein ring OSN recovery test). The keyword query is not at fault — re-running arxiv_fetch.py with LOOKBACK_HOURS=72 returns both. The window is anchored to submission time, but the arXiv search API only indexes a paper once it is announced, 1–3 days later. Every run therefore has a permanently-dropped tail, worst on Mon/Tue.

Plan

  • Replace the rolling "last N hours" window with arXiv's announcement bands, so each run selects exactly the batch that just became searchable.
  • Compute the band from the run time: find the most recent 20:00 ET announcement (Sun–Thu), take the latest 14:00 ET submission deadline at or before it as the band end, and the previous deadline as the band start.
  • Derive the ET↔UTC offset with zoneinfo rather than hard-coding it, so the band stays correct across DST.
  • Drop the Mon-72h / weekday-24h branch in the workflow — the band logic subsumes it, including the 3-day Fri→Mon weekend band.
  • Keep lookback_hours as a manual-dispatch escape hatch for test-fires and for a one-off backfill of already-dropped papers.
  • Add a --selftest mode covering the weekday and DST cases, and correct the docstring's "gapless" rationale to name the announcement anchor.
Detailed implementation plan

Affected Repositories

  • PyAutoMind (primary)

Branch Survey

Repository Current Branch Dirty?
./PyAutoMind main clean

Suggested branch: feature/arxiv-digest-announcement-window

Worktree: ~/Code/PyAutoLabs-wt/arxiv-digest-announcement-window/. A worktree is used deliberately so PyAutoMind's main checkout stays on main for registry operations — prompt_sync_push runs git add -A on whatever branch is checked out, so editing Mind's own CI on a branch in the main checkout would commit registry state to the feature branch.

The bug

.github/scripts/arxiv_fetch.py:77 keeps papers whose <published> (v1 submission timestamp) is within LOOKBACK_HOURS of now. Both missed papers were submitted Mon 2026-07-13 at 20:24 and 23:18 UTC — after the Mon 14:00 ET cut-off — so they entered the Tue 20:00 ET announcement batch and became searchable at 00:00 UTC Wed. Wednesday's 02:00 UTC run looked back 24 h to Tue 02:00 UTC; their Monday timestamps fall ~3 h the wrong side of that boundary. Tuesday's run could not have seen them either (not yet announced).

The docstring's claim that consecutive runs "cover disjoint day-bands (gapless)" only holds if papers are searchable at submission. They are not:

Run (02:00 UTC) Window queried (by submission) Batch actually announced by then Gap
Tue (24 h) Mon 02:00 → Tue 02:00 Fri 18:00 → Mon 18:00 UTC misses Fri 18:00 → Mon 02:00
Wed–Fri (24 h) prev 02:00 → 02:00 prev-day 18:00 → 18:00 UTC misses the 18:00 → 02:00 tail
Mon (72 h) Fri 02:00 → Mon 02:00 Thu 18:00 → Fri 18:00 UTC sweeps three days not yet announced

Monday's 72 h "weekend sweep" is the clearest case: weekend submissions are not announced until Mon 20:00 ET — after Monday's run — and Tuesday's 24 h window has already moved past them.

arXiv's actual schedule

Announcements fire 20:00 ET, Sun–Thu. Submission deadlines are 14:00 ET, Mon–Fri. Each announcement covers submissions between the previous deadline and its own:

Submitted between (ET) Announced (ET)
Mon 14:00 → Tue 14:00 Tue 20:00
Tue 14:00 → Wed 14:00 Wed 20:00
Wed 14:00 → Thu 14:00 Thu 20:00
Thu 14:00 → Fri 14:00 Sun 20:00
Fri 14:00 → Mon 14:00 Mon 20:00

The 02:00 UTC cron lands at 21:00–22:00 ET the previous day — just after that day's 20:00 ET announcement. So the Mon–Fri runs pick up the bands Thu→Fri, Fri→Mon, Mon→Tue, Tue→Wed, Wed→Thu. Their union is exactly one week with no overlap: disjoint and gapless, for real this time.

Implementation Steps

  1. .github/scripts/arxiv_fetch.py — add a pure announcement_band(now: datetime) -> tuple[datetime, datetime]:
    • convert now to ZoneInfo("America/New_York");
    • walk back to the most recent 20:00 ET on a Sun–Thu at or before it;
    • band_end = latest Mon–Fri 14:00 ET at or before that announcement (Sunday announcements resolve to Fri 14:00);
    • band_start = the previous Mon–Fri 14:00 ET before band_end (Monday resolves back to Friday, giving the 3-day weekend band);
    • return both as UTC.
  2. Replace the since/ts < since filter with band_start < ts <= band_end. Keep the <published> (v1) basis so v2 revisions still do not resurface.
  3. Keep LOOKBACK_HOURS as an explicit override: when set and non-empty, use the legacy rolling window (manual test-fires and the backfill). When unset, use the band. Record which mode ran in the JSON.
  4. Widen max_results 100 → 200 — the 3-day weekend band is the largest, and the cap is applied before band selection.
  5. Extend the output JSON with mode, band_start, band_end alongside the existing keys; keep since/until populated so the downstream Claude and Slack steps are untouched.
  6. .github/workflows/arxiv_papers.yml — delete the dow/72h/24h branch; pass LOOKBACK_HOURS only when the dispatch override is non-empty. Update the timing comment block to describe the announcement anchor.
  7. Add --selftest to arxiv_fetch.py: table-driven asserts over each weekday run, both DST regimes (a July run at UTC-4 and a January run at UTC-5), the weekend band, and the two missed papers as a regression case (Mon 20:24 UTC must fall inside the band a Wed 02:00 UTC run computes). Run it as a workflow step before the fetch.
  8. Rewrite the module docstring: the window is announcement-anchored; state why submission-anchored windows drop papers.

Backfill

After merge, one manual workflow_dispatch with lookback_hours=168 to sweep the week and recover what the current windows dropped. This runs the normal Claude + Slack path, so it posts a catch-up digest to #papers.

Testing

  • python3 .github/scripts/arxiv_fetch.py --selftest — band maths, no network.
  • A live run against the real API for the current band, checking both missed papers appear when the clock is faked to Wed 2026-07-15 02:00 UTC.
  • workflow_dispatch test-fire with an override to confirm the override path is intact.

Key Files

  • .github/scripts/arxiv_fetch.py — window logic, band function, selftest
  • .github/workflows/arxiv_papers.yml — lookback branch removal, selftest step, timing comment

Trade-offs

  • Band vs. state. A cross-run "seen IDs" store would also fix this, but it trades the current stateless design for a committed state file and its drift modes. The band keeps statelessness and gets exact disjointness rather than dedup-after-overlap.
  • Residual. Papers announced out of band (submission on hold, a cross-list added later) are still missed. Rare; a seen-ID store is the only real fix and is not worth it yet.
  • Jitter. GitHub cron only ever fires late. The band is stable anywhere from 20:00 ET to the next 20:00 ET, so it absorbs ~22 h of slip — far more tolerance than the ±0 the current rolling window really had.

Original Prompt

Click to expand starting prompt

arXiv #papers digest silently drops papers — window anchored to submission, not announcement

Type: bug
Target: pyautomind
Repos:

  • PyAutoMind
    Difficulty: medium
    Autonomy: safe
    Priority: normal
    Status: formalised

The #papers digest missed two strong-lensing papers announced on 2026-07-15
(arXiv:2607.12129, a lensed-arc candidate in MACS J0308.9+2645; arXiv:2607.12209,
the NGC 6505 Einstein ring OSN recovery test). Both are matched by the keyword
query — re-running .github/scripts/arxiv_fetch.py with LOOKBACK_HOURS=72
returns both — so the recall-first QUERY is not at fault.

Root cause: arxiv_fetch.py:77 keeps papers whose <published> (v1 submission
timestamp) falls within LOOKBACK_HOURS of now, but the arXiv search API only
indexes a paper once it is announced, which is 1–3 days after submission.
Both papers were submitted Mon 2026-07-13 at 20:24 and 23:18 UTC — after the
Mon 14:00 ET cut-off — so they went into the Tue 20:00 ET announcement batch and
became searchable at 00:00 UTC Wed 2026-07-15. Wednesday's 02:00 UTC run looked
back 24 h to Tue 02:00 UTC; their Monday timestamps fall ~3 h the wrong side of
that boundary. Tuesday's run could not have seen them either (not yet announced).
Permanently dropped, with no error and no gap in the heartbeat.

The module docstring's claim that consecutive runs "cover disjoint day-bands
(gapless)" only holds if papers are searchable at submission time. They are not,
so every run has a dropped tail, and Monday/Tuesday are worst:

Run (02:00 UTC) Window queried (by submission) Batch actually announced by then Gap
Tue (24 h) Mon 02:00 → Tue 02:00 Fri 18:00 → Mon 18:00 UTC misses Fri 18:00 → Mon 02:00
Wed–Fri (24 h) prev 02:00 → 02:00 prev-day 18:00 → 18:00 UTC misses the 18:00 → 02:00 tail
Mon (72 h) Fri 02:00 → Mon 02:00 Thu 18:00 → Fri 18:00 UTC sweeps three days not yet announced

Monday's 72 h "weekend sweep" is the clearest illustration: weekend submissions
are not announced until Mon 20:00 ET — after Monday's run — and Tuesday's 24 h
window has already moved past them.

Fix: anchor the window to arXiv's announcement bands rather than a rolling
"last N hours", preserving the existing stateless disjoint-band design and its
jitter tolerance; only the band edges change. At a 02:00 UTC run the freshly
announced batch is submissions from roughly 32 h to 8 h ago; the Monday run needs
the three-day Fri 14:00 ET → Mon 14:00 ET band. The ET→UTC offset shifts by an
hour across DST (14:00 ET = 18:00 UTC in EDT, 19:00 UTC in EST) and must be
computed, not hard-coded. Update the docstring's gapless-band rationale to state
the announcement anchor. Include a one-off backfill run to recover papers the
current windows have already dropped.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions