diff --git a/complete/2026/08/arxiv-inbox-pat-scope.md b/complete/2026/08/arxiv-inbox-pat-scope.md new file mode 100644 index 00000000..9dff1d8d --- /dev/null +++ b/complete/2026/08/arxiv-inbox-pat-scope.md @@ -0,0 +1,180 @@ +- issue: none — filed and shipped in one session, never routed through `/create_issue`. +- shipped: 2026-08-25 — PyAutoMind main 308d4fa (PR #317). The token half of the fix + was a GitHub account setting, not a commit, and has no SHA. +- classification: bug (PyAutoMind + PyAutoMemory) — fifth in the paper-management line, + and the first *regression* in it: it broke the feature #57 shipped the day before + (`arxiv-inbox-tier`), on that feature's very first live run. +- summary: the nightly arXiv digest's inbox-filing step silently lost every paper it + filed. `PAT_PYAUTOLABS` had write on the `*-template` repos `spawn_drift.yml` writes, + not on `PyAutoMemory` itself, so the push 403'd — and the step swallowed it as a + `::warning::` and exited 0. The run stayed green, Slack posted as normal, and the + knowledge board read "nothing waiting", indistinguishable from a quiet day. Fixed by + widening the PAT scope (human) and by making a denied push fail the step (PR #317). + +- the failure, exactly: run 32803698132, 2026-08-25 03:03 UTC. Every step green. The + inbox step's own log: + + inbox: appended:1, swept:0 + [main 3798728] inbox: appended:1, swept:0 (arXiv digest 2026-08-25 (Tue)) + remote: Permission to PyAutoLabs/PyAutoMemory.git denied to Jammy2211. + fatal: ... The requested URL returned error: 403 + + `append` worked, the commit was made, the push was refused three times, the commit + died with the runner. + +- root cause, and why it survived review: #57's record says "PyAutoMind's + `PAT_PYAUTOLABS` already existed (spawn_drift.yml) — the cross-repo write needed no + new secret". That checked the secret *existed*; it did not check its *scope* covered + the new target. The inbox step was the first thing in the org needing + `Contents: read-and-write` on `PyAutoMemory` itself. Nothing upstream of the push + hints at it, because the clone succeeds — read is unrestricted on a public repo, so + a green clone proves nothing about write. + +- what the code change does: the step's declared design was already "expected-absent + preconditions warn and exit 0; a genuinely broken script still fails the step". A + denied push is the second case and was wired as the first. It now errors and exits 1; + the missing-survivors and unset-PAT preconditions keep warn-and-exit-0. This does not + weaken "a cross-repo failure must never cost the morning's post" — the step runs + AFTER the Slack POST, so failing it cannot touch the digest, it only reddens the run. + An auth failure also short-circuits the retry loop: that retry is for a concurrent-push + race against the board and queue-action workflows, and a 403 cannot clear on a rebase. + +- traps: + - **a green clone is not write access.** The 403 lands on the push, several steps + after the point a reader assumes auth was proven. Any future cross-repo step should + be read as untested until a push has actually succeeded once. + - **you cannot test `arxiv_papers.yml` from a branch that edits it.** + `claude-code-action@v1` refuses to run when the workflow file differs from the + default branch's copy ("Workflow validation failed") — and then exits **success**. + The run continues with no `slack_payload.json`, the POST fails on the missing file, + and every later step including the inbox filing is skipped. Worse, the error printed + blames an expired `CLAUDE_CODE_OAUTH_TOKEN`, pointing at the wrong cause entirely. + Cost a wasted dispatch here (run 32846505925); now recorded as a comment at the + Claude step. Test by merging, or dispatch `main` and read the logs. + - **the backfill window must be 168h, not 24h.** The lookback is submission-anchored + (`arxiv_fetch.py` filters on `submittedDate`), so a paper announced today may have + been submitted days earlier — the PyAutoMind#79 trap, reachable again through the + recovery path. Both new error messages name 168 so the next reader does not + re-derive it. + - **adding a `draft/` file makes `dashboard.md` stale and reddens PR CI.** AGENTS.md + says to regenerate; the `refresh` check enforces it. Regenerate *after* merging the + base branch in, or the page is stale again the moment it lands. + - `intake`'s `--apply` is a **global** flag, before the subcommand + (`--mind . --apply dashboard`), while `--check` is a subcommand flag + (`--mind . dashboard --check`). They are not symmetric. + +- the backfill: `workflow_dispatch` on main, `lookback_hours=168` + (run 32847233221) — `appended:2, swept:0`, pushed clean. `arxiv-inbox.md` now holds + 2608.23534 and 2608.18224, both stamped with the run date, so each got a fresh 7-day + window rather than its original announcement date (`append` always stamps "today"). + +- validation: 197 PyAutoMind tests green; `lifecycle.py check` OK; `lifecycle.py index + --check` OK; `intake dashboard --check` current; YAML parses and the changed step + passes `bash -n`. PR #317 green on both `pull_request` runs. +- NOT proven live: the 403 path cannot be re-triggered now the token works, and the + branch-testing trap means no branch dispatch can reach that step. Those lines are + shell-syntax-checked and reasoned, not fired. The step is plain bash with no + dependency on `claude-code-action`, so the scheduled 02:00 UTC run on main is the + first genuine end-to-end exercise. +- still open: the board's empty state remains indistinguishable from a broken filing + run — the very thing the `#papers` empty-day heartbeat exists to prevent, with no + equivalent on the board. Split out as + `draft/feature/pyautomemory/inbox_board_staleness_signal.md`. +- affected-repos: + - PyAutoMind + - PyAutoMemory + +## Original prompt + +_As first filed on 2026-08-25 (commit 501b862), before the PAT scope was widened and +the backfill ran. `lifecycle.py record --prompt` folds an `active/` prompt; this task +never entered `active/`, so the draft is folded in by hand._ + +# arXiv inbox never fills — PAT_PYAUTOLABS cannot write to PyAutoMemory + +Type: bug +Target: PyAutoMemory +Repos: +- PyAutoMind +- PyAutoMemory +Difficulty: small +Autonomy: supervised +Priority: high +Status: formalised +Filed: 2026-08-25 + +The first live run of the arXiv inbox filing step (PyAutoMemory#57, shipped +2026-08-24) failed on its first night. The Slack digest posted normally, the +knowledge board shows "nothing waiting — the nightly arXiv digest fills this", +and the workflow run is **green**. + +## What actually happened + +PyAutoMind run [32803698132](https://github.com/PyAutoLabs/PyAutoMind/actions/runs/32803698132), +2026-08-25 03:03 UTC. Every step succeeded; the inbox step's own log says: + +``` +inbox: appended:1, swept:0 +[main 3798728] inbox: appended:1, swept:0 (arXiv digest 2026-08-25 (Tue)) + 1 file changed, 1 insertion(+) +remote: Permission to PyAutoLabs/PyAutoMemory.git denied to Jammy2211. +fatal: unable to access 'https://github.com/PyAutoLabs/PyAutoMemory.git/': The requested URL returned error: 403 +``` + +`scripts/inbox_actions.py append` worked. The commit was made. The **push** was +denied, three times, and the step exited 0 with a `::warning::`. The commit died +with the runner. + +## Root cause + +`PAT_PYAUTOLABS` does not grant write on `PyAutoLabs/PyAutoMemory`. + +Its only prior consumer is `spawn_drift.yml`, which writes to +`PyAutoMind-template` and `PyAutoMemory-template` — the *generated view* repos, +not the live Memory. The inbox step is the first thing in the org to need +`Contents: read-and-write` on `PyAutoMemory` itself, and the completion record's +"PyAutoMind's `PAT_PYAUTOLABS` already existed (spawn_drift.yml) — the +cross-repo write needed no new secret" checked that the *secret* existed, not +that its *scope* covered the new target. The clone succeeded (read is +unrestricted), so nothing upstream of the push hinted at it. + +## Remaining work (human-gated) + +1. **Grant the scope.** On the fine-grained PAT behind `PAT_PYAUTOLABS`, add + `PyAutoLabs/PyAutoMemory` with `Contents: read-and-write`. This is the whole + fix; nothing in either repo needs to change for filing to start working. +2. **Backfill the lost night.** Today's surviving paper is not recoverable from + the next scheduled run — the announcement band moves on and never revisits. + Re-file it with a `workflow_dispatch` of `arxiv_papers.yml` carrying + `lookback_hours=168`. **Not 24**: the lookback is submission-anchored + (`arxiv_fetch.py` sorts and filters on `submittedDate`), and a paper + announced today may have been submitted days earlier — the same trap as + PyAutoMind#79. `append` dedupes against both the inbox and the reading queue, + so an over-wide sweep is safe. + +## Already fixed on this branch + +`arxiv_papers.yml`'s inbox step no longer exits 0 on a failed push. The step's +design intent was already "expected-absent preconditions warn and exit 0; a +genuinely broken script still fails the step" — a 403 is the second case and was +wired as the first. The missing-survivors and unset-PAT preconditions keep their +warn-and-exit-0 behaviour. + +The "must never cost the morning's post" constraint is untouched: the step runs +*after* the Slack POST, so failing it cannot cost the digest — it only turns the +run red, which is the signal the failure lacked. + +An auth failure now also short-circuits the retry loop instead of burning three +identical attempts. The retry exists for a concurrent-push race against the +board and queue-action workflows; a 403 is not a race and will never clear on a +rebase. + +## Follow-up worth filing separately + +The board's empty state is indistinguishable from a broken filing run — exactly +the failure mode the `#papers` heartbeat was added to kill ("silence in #papers +always means a broken run, never a genuinely empty one", 2026-07-13). The board +has no equivalent: an inbox that is empty because nothing was filed reads the +same as one that is empty because arXiv was quiet. A "last filled: " line, +or an explicit quiet-day marker written by the digest, would close it. That is a +PyAutoMemory board change and a separate task. diff --git a/complete/index.md b/complete/index.md index ac3a4057..eb8dafc8 100644 --- a/complete/index.md +++ b/complete/index.md @@ -6,7 +6,7 @@ Token-light navigation over the finished-work records (schema: only then grep a dated bucket. Curators: edit the band between the CURATED markers; everything below GENERATED is rebuilt. -1132 records across 7 buckets. +1133 records across 7 buckets. ## Highlights @@ -22,6 +22,7 @@ _(curate hard-won records here — survives regeneration.)_ - [active-prompt-orphan-triage](2026/08/active-prompt-orphan-triage.md) - [aplt-output-drift-remaining-repos](2026/08/aplt-output-drift-remaining-repos.md) - [arcticpy-install-standardisation](2026/08/arcticpy-install-standardisation.md) +- [arxiv-inbox-pat-scope](2026/08/arxiv-inbox-pat-scope.md) — the nightly arXiv digest's inbox-filing step silently lost every paper it - [arxiv-inbox-tier](2026/08/arxiv-inbox-tier.md) — the nightly strong-lensing digest no longer ends in a Slack paste-block the - [astropy-cap-bump](2026/08/astropy-cap-bump.md) — closed manually post-merge — "Part of" in the PR bodies does not auto-close - [autoarray-adapt-images-precondition](2026/08/autoarray-adapt-images-precondition.md) diff --git a/dashboard.html b/dashboard.html index b8652b74..bd589d40 100644 --- a/dashboard.html +++ b/dashboard.html @@ -215,8 +215,7 @@

markdown version

Start here

-

Highest priority (filed as high) — showing 12 of 16

-

arXiv inbox never fills — PAT_PYAUTOLABS cannot write to PyAutoMemory🐛 bugpyautomemorysmallsupervisedhigh

+

Highest priority (filed as high) — showing 12 of 15

TRIAGE: needs manual review before routing❓ triagemediumsafehigh

Numba CPU likelihood phase 1: batched MGE convolution + operated-matrix caching✨ featureautoarraymediumsupervisedhigh

Rectangular mesh split: Bilinear (fast CPU default) vs RTU (advanced/GPU)✨ featureautoarraymediumsupervisedhigh

@@ -228,6 +227,7 @@

Highest priority (filed as high) — showing 12 of 16

Split lensing regimes: multi_galaxy / group / cluster (epic plan)📖 docsautolenstoo-largesupervisedhigh

Re-baseline the MGE imaging JIT profiling regression value🧪 testautolens_workspace_developertoo-largesupervisedhigh

Fix release JAX runtime compatibility and likelihood parity🐛 bughealth_fixestoo-largesupervisedhigh

+

Fix JIT quick-update visualization output regressions🐛 bughealth_fixestoo-largesupervisedhigh

Quick wins (small enough, and safe enough to run unattended)

@@ -253,13 +253,14 @@

Planned markdown version

141 filed prompts, not started — sorted most-pickable first (priority, then size). 25 of them belong to an epic and are listed only under Epics below.

-feature — 27 +feature — 28

Give PyAutoFit searches a seed — today no search can be made…✨ featureautofitmediumsupervisedmedium

Brain board follow-ups: what real mornings surface✨ featurepyautobrainsmallsupervisednormal

+

The knowledge board's empty inbox should distinguish quiet from broken✨ featurepyautomemorysmallsupervisednormal

Can create a list of InversionMatrix objects for each dataset✨ featureautoarraymediumsupervisednormal

Tune cluster-scale JOSS benchmarks toward their 5-minute targets✨ featureautolens_workspacemediumsupervisednormal

Token-light wiki index over the complete/ archive✨ featurepyautomindmediumsupervisednormal

@@ -283,8 +284,7 @@

Backlog

Teach repos_sync --write to stamp organ config surfaces✨ featurepyautomindhardsupervisedlow

-bug — 23 - +bug — 22

Fix release JAX runtime compatibility and likelihood parity🐛 bughealth_fixestoo-largesupervisedhigh

Fix JIT quick-update visualization output regressions🐛 bughealth_fixestoo-largesupervisedhigh

Fix release result/sample parameter-path regressions🐛 bughealth_fixestoo-largesupervisedhigh

@@ -401,8 +401,8 @@

Backlog 2026-08-25 filed -arXiv inbox never fills — PAT_PYAUTOLABS cannot write to PyAutoMemory - +The knowledge board's empty inbox should distinguish quiet from broken + 2026-08-24 diff --git a/dashboard.md b/dashboard.md index d6387ebf..3c755e37 100644 --- a/dashboard.md +++ b/dashboard.md @@ -49,15 +49,7 @@ anything you could not verify. ## Start here -**Highest priority** (filed as `high`) — showing 12 of 16 - -
📋 arXiv inbox never fills — PAT_PYAUTOLABS cannot write to PyAutoMemory — pyautomemory · small · supervised · high - -``` -/start_dev draft/bug/pyautomemory/arxiv_inbox_never_filled_pat_scope.md -``` - -
+**Highest priority** (filed as `high`) — showing 12 of 15
📋 TRIAGE: needs manual review before routing — medium · safe · high @@ -147,6 +139,14 @@ anything you could not verify.
+
📋 Fix JIT quick-update visualization output regressions — health_fixes · too-large · supervised · high + +``` +/start_dev draft/bug/health_fixes/jit_visualization_outputs.md +``` + +
+ **Quick wins** (small enough, and safe enough to run unattended)
📋 Defer the eager scipy.sparse import in derivative_util (~0.10 s of import) — libraries · small · safe · normal @@ -264,7 +264,7 @@ Scoped but not started; some are not yet prompt files. Full detail in [`planned. **141** filed prompts, not started. Each section is sorted most-pickable first (priority, then size). **25** of them belong to an epic and are listed only under [Epics](#epics) below.
-feature — 27 +feature — 28
📋 Numba CPU likelihood phase 1: batched MGE convolution + operated-matrix caching — autoarray · medium · supervised · high @@ -314,6 +314,14 @@ Scoped but not started; some are not yet prompt files. Full detail in [`planned.
+
📋 The knowledge board's empty inbox should distinguish quiet from broken — pyautomemory · small · supervised · normal + +``` +/start_dev draft/feature/pyautomemory/inbox_board_staleness_signal.md +``` + +
+
📋 Can create a list of InversionMatrix objects for each dataset — autoarray · medium · supervised · normal ``` @@ -485,15 +493,7 @@ Scoped but not started; some are not yet prompt files. Full detail in [`planned.
-bug — 23 - -
📋 arXiv inbox never fills — PAT_PYAUTOLABS cannot write to PyAutoMemory — pyautomemory · small · supervised · high - -``` -/start_dev draft/bug/pyautomemory/arxiv_inbox_never_filled_pat_scope.md -``` - -
+bug — 22
📋 Fix release JAX runtime compatibility and likelihood parity — health_fixes · too-large · supervised · high @@ -1242,7 +1242,7 @@ The 50 newest things to happen to the work in hand, newest first — issued, par | Date | Event | Task | |------|-------|------| -| 2026-08-25 | filed | arXiv inbox never fills — PAT_PYAUTOLABS cannot write to PyAutoMemory | +| 2026-08-25 | filed | The knowledge board's empty inbox should distinguish quiet from broken | | 2026-08-24 | filed | wiki-currency's --check-version gate rots on every library main merge | | 2026-08-24 | filed | interferometer/jax_grad/gradient.py: eager and jitted likelihoods… | | 2026-08-24 | filed | euclid: CRLF has reached the HPC submit scripts AGENTS.md warns about | diff --git a/draft/bug/pyautomemory/arxiv_inbox_never_filled_pat_scope.md b/draft/bug/pyautomemory/arxiv_inbox_never_filled_pat_scope.md deleted file mode 100644 index 968d0e1c..00000000 --- a/draft/bug/pyautomemory/arxiv_inbox_never_filled_pat_scope.md +++ /dev/null @@ -1,122 +0,0 @@ -# arXiv inbox never fills — PAT_PYAUTOLABS cannot write to PyAutoMemory - -Type: bug -Target: PyAutoMemory -Repos: -- PyAutoMind -- PyAutoMemory -Difficulty: small -Autonomy: supervised -Priority: high -Status: formalised -Filed: 2026-08-25 - -The first live run of the arXiv inbox filing step (PyAutoMemory#57, shipped -2026-08-24) failed on its first night. The Slack digest posted normally, the -knowledge board shows "nothing waiting — the nightly arXiv digest fills this", -and the workflow run is **green**. - -## What actually happened - -PyAutoMind run [32803698132](https://github.com/PyAutoLabs/PyAutoMind/actions/runs/32803698132), -2026-08-25 03:03 UTC. Every step succeeded; the inbox step's own log says: - -``` -inbox: appended:1, swept:0 -[main 3798728] inbox: appended:1, swept:0 (arXiv digest 2026-08-25 (Tue)) - 1 file changed, 1 insertion(+) -remote: Permission to PyAutoLabs/PyAutoMemory.git denied to Jammy2211. -fatal: unable to access 'https://github.com/PyAutoLabs/PyAutoMemory.git/': The requested URL returned error: 403 -``` - -`scripts/inbox_actions.py append` worked. The commit was made. The **push** was -denied, three times, and the step exited 0 with a `::warning::`. The commit died -with the runner. - -## Root cause - -`PAT_PYAUTOLABS` does not grant write on `PyAutoLabs/PyAutoMemory`. - -Its only prior consumer is `spawn_drift.yml`, which writes to -`PyAutoMind-template` and `PyAutoMemory-template` — the *generated view* repos, -not the live Memory. The inbox step is the first thing in the org to need -`Contents: read-and-write` on `PyAutoMemory` itself, and the completion record's -"PyAutoMind's `PAT_PYAUTOLABS` already existed (spawn_drift.yml) — the -cross-repo write needed no new secret" checked that the *secret* existed, not -that its *scope* covered the new target. The clone succeeded (read is -unrestricted), so nothing upstream of the push hinted at it. - -## Resolved 2026-08-25 - -1. **Scope granted.** `PAT_PYAUTOLABS` now carries write on - `PyAutoLabs/PyAutoMemory`. No code change was needed for filing to work. -2. **Backfill done.** `workflow_dispatch` of `arxiv_papers.yml` on `main` with - `lookback_hours=168` - ([run 32847233221](https://github.com/PyAutoLabs/PyAutoMind/actions/runs/32847233221)) - filed `appended:2, swept:0` and pushed clean. PyAutoMemory `arxiv-inbox.md` - now holds: - - ``` - 2026-08-25 — Strong Lensing Cosmology with Population-level Calibrated Neural Ratio Estimation — 2608.23534 - 2026-08-25 — Mapping the Information Geometry of an Unresolved Dark Matter Population using a Differentiable Strong Lensing Simulator — 2608.18224 - ``` - - 168, not 24: the lookback is submission-anchored (`arxiv_fetch.py` sorts and - filters on `submittedDate`), so a paper announced today may have been - submitted days earlier — the PyAutoMind#79 trap. `append` dedupes against - both the inbox and the reading queue, so an over-wide sweep is safe, and it - stamps every backfilled line with *today's* date, giving each a fresh 7-day - window rather than its original announcement date. - -## Trap: you cannot test this workflow from a branch that edits it - -The first backfill attempt was dispatched from this branch so the new -fail-loudly path would be exercised. It cannot be. `claude-code-action@v1` -refuses to run when the workflow file differs from the default branch's copy: - -``` -Skipping action due to workflow validation: Workflow validation failed. The -workflow file must exist and have identical content to the version on the -repository's default branch. -``` - -The action then **exits `success`**, so the run continues with no -`slack_payload.json`, the POST step fails on the missing file, and every later -step — the inbox filing included — is skipped. Consequence for anyone editing -`arxiv_papers.yml`: a branch dispatch exercises the fetch and the band self-test -and nothing past them, and its failure says "the Claude summarise step failed to -write it (check for an expired CLAUDE_CODE_OAUTH_TOKEN)", which points at the -wrong cause entirely. Test workflow-file changes by merging them, or dispatch -`main` and read the step logs. - -Consequence for this change specifically: the 403 path it fixes cannot be -re-triggered now that the token works, so the new branch is shell-syntax-checked -and reasoned, not live-fired. The step it touches is plain bash with no -dependency on `claude-code-action`, so a merge to `main` is what proves it. - -## Already fixed on this branch - -`arxiv_papers.yml`'s inbox step no longer exits 0 on a failed push. The step's -design intent was already "expected-absent preconditions warn and exit 0; a -genuinely broken script still fails the step" — a 403 is the second case and was -wired as the first. The missing-survivors and unset-PAT preconditions keep their -warn-and-exit-0 behaviour. - -The "must never cost the morning's post" constraint is untouched: the step runs -*after* the Slack POST, so failing it cannot cost the digest — it only turns the -run red, which is the signal the failure lacked. - -An auth failure now also short-circuits the retry loop instead of burning three -identical attempts. The retry exists for a concurrent-push race against the -board and queue-action workflows; a 403 is not a race and will never clear on a -rebase. - -## Follow-up worth filing separately - -The board's empty state is indistinguishable from a broken filing run — exactly -the failure mode the `#papers` heartbeat was added to kill ("silence in #papers -always means a broken run, never a genuinely empty one", 2026-07-13). The board -has no equivalent: an inbox that is empty because nothing was filed reads the -same as one that is empty because arXiv was quiet. A "last filled: " line, -or an explicit quiet-day marker written by the digest, would close it. That is a -PyAutoMemory board change and a separate task. diff --git a/draft/feature/pyautomemory/inbox_board_staleness_signal.md b/draft/feature/pyautomemory/inbox_board_staleness_signal.md new file mode 100644 index 00000000..8dab2954 --- /dev/null +++ b/draft/feature/pyautomemory/inbox_board_staleness_signal.md @@ -0,0 +1,66 @@ +# The knowledge board's empty inbox should distinguish quiet from broken + +Type: feature +Target: PyAutoMemory +Repos: +- PyAutoMemory +Difficulty: small +Autonomy: supervised +Priority: normal +Status: formalised +Filed: 2026-08-25 + +Split out of `complete/2026/08/arxiv-inbox-pat-scope.md`, which fixed the filing +bug but deliberately left this open. + +## The gap + +The board renders an empty arXiv inbox as: + + arXiv inbox (suggested overnight — un-acted papers lapse after 7 days) + nothing waiting — the nightly arXiv digest fills this. + +That line is shown for two completely different states: + +- arXiv genuinely announced no strong-lensing papers; or +- the digest ran and the filing failed, so papers were lost. + +On 2026-08-25 it was the second, and nothing on the page said so. The reader has +no way to tell without opening GitHub Actions. + +## Why this is a known-solved problem here + +`arxiv_papers.yml` already fixed the identical ambiguity on the Slack side. From +its header comment (user, 2026-07-13): + +> Empty days STILL post a short "no new strong-lensing papers today" heartbeat — +> so silence in #papers always means a broken run, never a genuinely empty one. + +The board has no equivalent. The digest's own invariant stops at Slack and does +not reach the surface the paper-management feature actually asks the human to +use. + +## Task + +Give the inbox a freshness signal so an empty board is readable. Options, in +rough order of cost: + +- **`last filled:` line** — the board renders the date of the most recent + successful `append`/`sweep`. An empty inbox dated today is quiet; one dated + four days ago is suspect. Cheapest, no new state if it can be derived from + `arxiv-inbox.md`'s git history or a marker the digest writes. +- **Explicit quiet-day marker** — the digest writes a dated "no papers" line on + genuinely empty days, exactly mirroring the Slack heartbeat. Makes absence + *positive* evidence, at the cost of a line the sweep must then age out. +- **Freshness warning** — the board flags an inbox with no write in N weekdays. + Strongest signal, needs a threshold nobody has tuned yet. + +Prefer whichever needs no new state file. Note that `scripts/inbox_actions.py` +is the single owner of the line format, the window and every transition — a +marker line must go through it, not through `board.py` or a workflow's shell. + +## Note on the sibling failure mode + +PR PyAutoMind#317 made the *workflow* fail loudly when the push is denied, so a +future filing failure reddens the run. This task covers the other half: the +board should be honest even when nobody is reading CI.