ci: add stale workflow to triage inactive issues and PRs#5668
Conversation
Keep an unhealthy MemoryVectorStore instance available for health reporting instead of discarding it as disabled. This lets health checks report a degraded/down vector-store state while preserving focused regression coverage for initializer behavior.
Add Arch-specific package installation and NVIDIA runtime configuration to the Docker setup guide. Cover passthrough verification, the NVIDIA Compose overlay, and the distinction between GPU passthrough and CUDA-backed model serving Refs #831
chore: sync upstream changes from odysseus-dev/odysseus
fix(docker): bump Docker CLI to a patched release
fix(docker): bump Docker CLI to a patched release
* feat(models): define capability schema and readers * fix(models): harden Google catalog probing Restrict native catalog probing to the Gemini host, keep provider keys out of request URLs, filter non-chat model resources, and preserve the manual refresh default in the built-in Google add flow.
…(#5474) * security(url-safety): reject RFC 6598 shared address space in strict mode Strict mode (block_private=True) is a full SSRF lockdown, but it only rejected is_private and is_loopback targets. CPython does not classify RFC 6598 shared/CGNAT space (100.64.0.0/10) as is_private (it is "shared", not "private"), so a public redirect into 100.64.0.1 passed the per-hop guard and still issued the request to a potentially internal CGNAT service. not is_global would also exclude it, but only on CPython 3.11.10+/3.12.4+/ 3.13+; the CI matrix runs 3.11/3.12, so reject the range explicitly to stay correct across patch levels and the 3.14 runtime image. Default local-first mode is unchanged. Adds strict-mode coverage for shared, non-global, and public targets. * docs(url-safety): correct CGNAT is_global rationale in strict-mode comment The prior comment claimed `not is_global` catches 100.64.0.0/10 only on CPython 3.11.10+/3.12.4+/3.13+. That is inaccurate for CGNAT: is_global is False for 100.64.0.1 on every supported version (verified 3.10-3.14). The version-fragility applies to other ranges gh-113171 touched, not CGNAT. The explicit range reject is still the right choice; restate the reason as is_private not covering shared space, and not coupling strict mode to is_global's broader, cross-version definition. No behavior change.
…… (#5491) * fix(llm): enhance fallback logic to handle empty completions and improve metadata handling * fix(llm): stream tool call deltas immediately
Slice 2f of the route-domain reorganization (#4082/#4071, per specs/architecture-runtime-inventory.md §6.3). Moves note_routes.py into routes/note/, leaving a backward-compat sys.modules shim at the old path. Pure file reorganization, no behavior change. The shim uses sys.modules replacement (same pattern as the merged gallery #4903, research #4975, memory #5007, history #5090, and contacts #5227 slices) so that `import routes.note_routes`, `from routes.note_routes import X`, `importlib.import_module(...)`, and the `import ... as note_routes` + `monkeypatch.setattr(note_routes, "SessionLocal", ...)` pattern used by test_note_reminder_fire_scope.py / test_notes_fail_closed_auth.py all operate on the same module object the application uses. The canonical module does NOT depend on the shim — routes/note/note_routes.py imports only from core/, src/, and stdlib. The outbound email cross-domain imports (routes.email_routes._get_email_config, routes.email_helpers. _send_smtp_message) are function-local lazy imports that keep resolving through the email module's own path (email is not yet migrated). One source-introspection test site repointed to the new canonical path: - test_model_helper_owner_scope.py (shared with history; history entry already repointed in #5090, note entry repointed here) Adds tests/test_note_routes_shim.py to pin the sys.modules shim contract (legacy and canonical paths resolve to the same module object; monkeypatch via legacy alias reaches the canonical module). Verified: compileall clean; full suite 4487 passed, 3 skipped.
|
I pushed a safer rollout for the stale workflow. Scheduled runs now default to dry-run and publish a linked candidate queue in the Actions summary, with the two tiers running in separate API windows. We can dogfood the queue for a few weeks, tune exemptions when something should stay open, then set |
RosenTomov
left a comment
There was a problem hiding this comment.
Looks good. Ran the "how to test" checks from the description and also dispatched stale.yml as a workflow on a throwaway repo (dry run, thresholds zeroed so fresh items register).
https://github.com/RosenTomov/actions-test/actions/runs/29816100643 t1
https://github.com/RosenTomov/actions-test/actions/runs/29816132590 t2
The 60-day tier-2 threshold already defers the risky cohort: the oldest review-ready PR is 47 days idle, so nothing in that group can close before day 27. That runway is the rollout period, and it needs no flag anyone has to remember to flip. Drop STALE_AUTOMATION_MODE and the live-run guard. The schedule runs live; workflow_dispatch with dry_run still previews on demand without changing anything, and a workflow can be disabled from the Actions tab if it needs pausing. Size the dry-run budget separately: the action counts an operation per label and per comment even when debug-only suppresses the API call, and a preview labels nothing, so its candidate set is the whole backlog rather than the day's arrivals. A live-sized cap would silently truncate it. Nothing is written, so the cost is the per-page reads only. Publish the queue on every run, not just previews, so a live run leaves a record of what it touched. Include title, last-activity date, and counts, all already present in the action's output.
|
Kept the cron split, the tier input, and the queue summary. The cron split in particular is better than what I had: I was treating the combined 1000 req/hr token limit as a constraint to design around, and separate hourly windows just removes it. I did drop The 60-day tier-2 threshold already does what the flag was protecting against. The oldest review-ready PR is 47 days idle, so the earliest any of that cohort can be marked is day 13 and the earliest any can close is day 27. Tier-2 PRs marked on day 0: zero. The four-week runway is already in the number, and it costs nothing to maintain. Against that, a mode variable is a standing obligation. If nobody flips it, the workflow sits inert while the merged PR, the workflow file, and the
The one place the flag genuinely bought time is issues: 229 marked on day 0, first closures on day 14. That is the fastest-moving part of this. If that is too quick, the honest lever is the threshold rather than a mode, and issues 45 to 60 would give roughly the same runway as the PR tiers with a one-line change. I left it at 45 because issues are the cheapest thing to get wrong (nothing is lost, reopening is trivial, 14 days of notice, any comment cancels), but I have no strong attachment to the number and would take 60 if you prefer. Two other changes in the same commit. The dry-run budget is now sized separately, because the action counts an operation per label and per comment even when |
|
Two things on policy, then two concrete P2s. First, the Actions variable is not owner-only; anyone with write access can manage it. But it still gives us a deliberate review period before this goes live. The native workflow switch is fine as an emergency stop once we know it is safe, but it disables previews too, so it does not replace a controlled rollout. Second, I do not think Issues and PRs should have the same posture. PRs are implementation attempts; Issues are the open map of the problems themselves. I am more willing to close an abandoned implementation than close a useful problem as Also, the workflow is broader than stale status plus failing checks and no author action. Tier 2 includes review-ready PRs, and any non-exempt Issue can be marked after 45 quiet days and closed 14 days later. It does not distinguish reporter inactivity from maintainer inactivity. There are also two P2s in the current implementation:
My main concern is that we are racing ourselves. We would be putting good problems onto a deterministic closure path before both the workflow and the thing meant to help us review it are reliable. |
Rework to a single actions/stale step selecting only PRs where the ball
is with the contributor:
any-of-pr-labels: needs work, merge conflict, stale pr
Clean, review-ready PRs carry none of these, so review latency on our
side can no longer put a PR on the closure path. Verified against the
live queue: the selector marks 165 PRs and leaves all 613 review-ready
and 8 approved PRs untouched. Issues stay age-based (45d), the
whole-backlog case.
Collapsing the two tiers into one invocation removes the shared _state
cursor hazard by construction, so a truncated run is now safe and just
resumes next time.
Fixes confirmed by running the action locally:
- operations-per-run sized at the real cost. A newly stale item consumes
4 counted operations (2 reads that run even under debug-only, 2
writes), so a run makes real API calls in dry mode too. 800 keeps both
modes under the GITHUB_TOKEN 1000/hr limit; the backlog drains over a
few daily runs via the cache cursor.
- the review queue shows created_at, not updated_at. The action rewrites
updated_at to the run time when it marks an item, so the previous
"last activity" column showed today's date for every marked row.
- the summary strips the action's output to {number,title,created_at,
pull_request} before use. That output serialises the full option set,
repo token included; the raw value is never echoed.
Scheduled runs stay dry until STALE_AUTOMATION_MODE=live, giving a review
period before anything closes.
|
Both confirmed by running the action locally: at cap 40 it marked 10 items for 41 ops, each showing Reworked to a single step. It now selects PRs only where the ball is with the contributor ( |
6c3b6d3 to
d8a2059
Compare
Summary
Adds
.github/workflows/stale.yml, a two-tieractions/staleconfiguration that marks inactive issues and PRs with aninactivelabel, comments to say why, and closes them 14 days later if nothing changes. No existing workflow is modified.inactiveafterneeds workWhat the first run would actually touch, as of 2026-07-21. These drift daily, so treat them as a snapshot rather than fixed numbers:
Nothing is closed on the first run. The earliest anything could close is 14 days after it is marked.
The two tiers exist because the backlog contains two very different populations:
Tier 1, PRs labelled
needs work.check-pr-description.jsapplies that label when a PR body is missing required template sections and removes it automatically once fixed, re-checking on every push. 146 open PRs still carry it, median idle 44 days. The author was told exactly what to fix and did not come back.Tier 2, everything else. Of the 232 tier-2-eligible PRs idle past 30 days, 219 carry
ready for reviewand 209 are clean and unconflicted. Those are stalled on review latency, not on the contributor. That is why tier-2 PRs are set at 60 days rather than 30, and why the tier-2 message explicitly says that waiting on a maintainer review is a valid reason to keep a PR open.The numbers are proposals. They are single-line edits. If you want tier 2 at 30 days to clear the backlog a month sooner, or issues left alone entirely, say so and I will change them.
Some details worth knowing before this runs:
inactiveand resets the clock.inactive, deliberately not the action's defaultStale, because this repo already has astale prlabel that means "out of sync with base, needs a rebase" and is owned by theFlag unmergeable PRsjob.operations-per-runis load-bearing and commented as such in the file: 400 for tier 1, 600 for tier 2.actions/staleshares one hardcoded Actions-cache key across invocations and records every item it examines, so a step that exhausts its budget leaves behind a cursor that makes the next step skip those items. A step that completes a full pass clears the cursor. Tier 2 gets the larger budget because it carries the issue backlog. Combined, the two stay at or under the 1000 requests/hourGITHUB_TOKENlimit, and only the first few runs come close.blocked,help wanted,good first issue, plussecurityandpinnedreserved as escape hatches.Please run the workflow manually with
dry_run: true(the default) from the Actions tab before letting the schedule fire. That reports what it would touch without changing anything.Linked Issue
Fixes #5667
Type of Change
Checklist
How to Test
Static validation, reproducing
.github/workflows/workflow-security.ymlat the same pinned versions. Fetch actionlint 1.7.12 using the version and checksum this repo's CI already pins, then run it against the new file; install zizmor 1.25.2, the same version the CI job installs, and run it with--offline --min-severity=low.Both pass with no findings. zizmor is also clean under
--persona=pedantic, which is stricter than the CI gate.Behavioural check, and the one I would like you to run before this is enabled:
staleworkflow.dry_runticked (it defaults to true).days-before-*values and re-run the dry run.The schedule is daily at 03:00 UTC. Until you are satisfied with the dry run, the workflow can be disabled from the Actions tab without reverting the merge.