pyauto-arxiv-papers #2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: pyauto-arxiv-papers | |
| # A daily digest of new strong-lensing papers from arXiv, summarised by Claude | |
| # and posted to Slack #papers. Sibling of morning_status.yml (the commit | |
| # digest) — same three-step shape: fetch data → Claude summarises into | |
| # slack_payload.json → POST to a Slack incoming webhook. The only new secret is | |
| # the #papers webhook; the Claude step reuses CLAUDE_CODE_OAUTH_TOKEN (the | |
| # Claude subscription OAuth token already set for the commit digest — no | |
| # Anthropic API key, no metered billing). | |
| # | |
| # Scope (user, 2026-07-10): STRONG LENSING ONLY — astro-ph.CO / astro-ph.GA | |
| # papers whose abstract/title names strong lensing (tightest signal). Format: | |
| # CURATED — 1–3 AI-chosen highlights with short summaries, then a compact list. | |
| # Cadence: weekday mornings, skip silently on empty (no quiet-day post). | |
| # | |
| # Timing: arXiv announces new astro-ph at 20:00 US Eastern, Sun–Thu, which is | |
| # ~01:00 UK (00:00 UTC in summer / 01:00 UTC in winter) — the papers are live in | |
| # the small hours, NOT the morning. GitHub cron only ever jitters *later* (0–3 h | |
| # under load), never earlier, so there is no way to pin an exact delivery time; | |
| # the nominal is set early enough that even a worst-case slip still lands before | |
| # a UK scientist wakes. Nominal 02:00 UTC (03:00 BST / 02:00 GMT) → worst case | |
| # ~05:00–06:00 UK, so the digest is already waiting in #papers over morning | |
| # coffee, ~2 h after the overnight announcement. Mon–Fri only: arXiv does not | |
| # announce at weekends, and Monday's run uses a 72 h look-back to sweep the | |
| # weekend's submissions. | |
| on: | |
| schedule: | |
| - cron: "0 2 * * 1-5" | |
| workflow_dispatch: | |
| inputs: | |
| lookback_hours: | |
| description: "Override the look-back window (hours) for a manual test-fire. Blank = the normal weekday logic (24h, 72h on Mondays)." | |
| required: false | |
| default: "" | |
| permissions: | |
| contents: read | |
| # claude-code-action@v1 needs OIDC to mint a short-lived GitHub token at | |
| # startup (setupGitHubToken → getOidcToken); without it the action exits | |
| # before reaching the prompt. Same requirement as morning_status.yml. | |
| id-token: write | |
| jobs: | |
| arxiv-papers: | |
| runs-on: ubuntu-latest | |
| steps: | |
| # claude-code-action runs `git config` in the workspace at startup and | |
| # dies with "fatal: not in a git directory" if nothing is checked out — | |
| # so the checkout is load-bearing even though the digest only reads the | |
| # arXiv API. (Also gives the fetch step its committed helper script.) | |
| - uses: actions/checkout@v4 | |
| - name: Fetch new strong-lensing papers from arXiv | |
| id: fetch | |
| run: | | |
| set -euo pipefail | |
| # Monday sweeps Fri-evening → Sunday submissions (72 h); other | |
| # weekdays take the previous day (24 h). Windows anchored to wall-clock | |
| # `now`, so consecutive runs cover disjoint day-bands (gapless; cron | |
| # jitter yields at worst a rare boundary duplicate, never a gap). | |
| # A manual dispatch may override the window (so a test-fire isn't | |
| # hostage to today's possibly-empty band); otherwise use the weekday | |
| # logic. | |
| override="${{ github.event.inputs.lookback_hours }}" | |
| if [ -n "$override" ]; then | |
| export LOOKBACK_HOURS="$override" | |
| else | |
| dow=$(date -u +%u) # 1=Mon … 7=Sun | |
| if [ "$dow" = "1" ]; then | |
| export LOOKBACK_HOURS=72 | |
| else | |
| export LOOKBACK_HOURS=24 | |
| fi | |
| fi | |
| export UK_DATE="$(TZ=Europe/London date '+%Y-%m-%d (%a)')" | |
| python3 .github/scripts/arxiv_fetch.py | |
| count=$(python3 -c "import json;print(json.load(open('arxiv_papers.json'))['count'])") | |
| echo "count=$count" >> "$GITHUB_OUTPUT" | |
| echo "Fetched $count strong-lensing paper(s) in the last ${LOOKBACK_HOURS}h." | |
| - name: Summarise papers with Claude | |
| # Skip the Claude call entirely on empty days — no post, no usage. | |
| if: ${{ steps.fetch.outputs.count != '0' }} | |
| uses: anthropics/claude-code-action@v1 | |
| with: | |
| # Claude subscription OAuth token — NOT an API key. Reused from the | |
| # commit digest; a once-daily summary of a handful of abstracts is | |
| # negligible against the subscription's usage allowance. | |
| claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} | |
| # Default-hidden output masks real failures (an expired token reads as | |
| # "slack_payload.json missing" in the POST step) — surface everything. | |
| show_full_output: true | |
| # Default permission mode denies all writes, so Claude could summarise | |
| # but not save slack_payload.json. Allow only Write; the runner's | |
| # GITHUB_TOKEN is contents:read, so nothing here can leave the runner | |
| # except via the POST step. | |
| claude_args: '--allowedTools "Write"' | |
| prompt: | | |
| Read `arxiv_papers.json` in this directory. It has shape: | |
| { "uk_date": "YYYY-MM-DD (Mon)", | |
| "since": "<ISO-UTC>", | |
| "until": "<ISO-UTC>", | |
| "count": <int>, | |
| "papers": [ { "title": "<title>", | |
| "authors": ["<name>", ...], | |
| "abstract": "<full abstract>", | |
| "url": "https://arxiv.org/abs/XXXX.XXXXXvN", | |
| "primary_category": "astro-ph.CO", | |
| "published": "<ISO-UTC submission date>" }, ... ] } | |
| Each paper is a new strong-gravitational-lensing paper announced on | |
| arXiv in the last day (astro-ph.CO / astro-ph.GA). | |
| Audience: strong-lensing researchers (working astronomers) reading | |
| #papers over their morning coffee. Tone: concise, scientific, | |
| plain — say what the paper is *about* and why it might matter, not | |
| marketing. | |
| First, DROP anything that is clearly not genuinely about strong | |
| gravitational lensing despite matching the keyword search (e.g. a | |
| weak-lensing or microlensing paper that merely mentions the phrase | |
| "strong lensing" in passing, or a paper where lensing is incidental). | |
| Use the abstract to judge. If that leaves nothing, follow the | |
| empty-day rule below. | |
| Then produce a Slack post: | |
| - HIGHLIGHTS: pick the 1–3 most notable surviving papers. For each, | |
| write a 2–3 sentence plain-language summary (what they did + the | |
| headline result / why it's interesting). Lead each with the | |
| title in bold and a link. | |
| - THE REST: a compact bulleted list of the remaining surviving | |
| papers, one line each: `• <title> — <first author> et al. (<link>)`. | |
| (If ≤3 papers total, everything is a highlight and this list is | |
| omitted.) | |
| Convert to Slack mrkdwn: links are `<url|text>`, bold is `*text*`. | |
| Write `slack_payload.json` in the repo root with this exact shape: | |
| { | |
| "text": "New strong-lensing papers — <uk_date>", | |
| "blocks": [ | |
| { "type": "header", | |
| "text": { "type": "plain_text", | |
| "text": "New strong-lensing papers — <uk_date>" } }, | |
| { "type": "section", | |
| "text": { "type": "mrkdwn", | |
| "text": "<intro: e.g. '*N* new strong-lensing paper(s) on arXiv today.'>" } }, | |
| { "type": "section", | |
| "text": { "type": "mrkdwn", | |
| "text": "*<title>* (<link>)\n<2–3 sentence summary>" } } | |
| ] | |
| } | |
| Add one section block per highlight, then (if any) a final section | |
| block holding the "*More:*" bulleted list of the rest. | |
| IF, after dropping off-topic papers, NOTHING genuinely about strong | |
| lensing remains: do NOT write slack_payload.json at all (leave it | |
| absent). The POST step skips when the file is missing — an empty day | |
| makes no post. | |
| Do not POST anything yourself, do not commit, do not modify any | |
| other file. | |
| - name: POST to Slack | |
| if: ${{ steps.fetch.outputs.count != '0' }} | |
| env: | |
| SLACK_WEBHOOK_URL: ${{ secrets.PYAUTO_PAPERS_WEBHOOK_URL }} | |
| run: | | |
| # No payload = a genuinely empty day (fetch found rows, but Claude | |
| # judged none genuinely on-topic). Skip silently, do not fail. | |
| if [ ! -s slack_payload.json ]; then | |
| echo "No slack_payload.json — nothing genuinely on-topic today, skipping post." | |
| exit 0 | |
| fi | |
| if [ -z "${SLACK_WEBHOOK_URL:-}" ]; then | |
| echo "::error::PYAUTO_PAPERS_WEBHOOK_URL not set — create the #papers Slack incoming webhook and add it as a repo secret." | |
| exit 1 | |
| fi | |
| curl -sS -X POST \ | |
| -H "Content-Type: application/json" \ | |
| --data @slack_payload.json \ | |
| --fail-with-body \ | |
| "$SLACK_WEBHOOK_URL" |