Skip to content

feat(remove-outputs): tag-scoped retention for limit and age - #313

Merged
timdegroot1996 merged 3 commits into
mainfrom
remove-outputs-with-tag
Sep 13, 2026
Merged

feat(remove-outputs): tag-scoped retention for limit and age #313
timdegroot1996 merged 3 commits into
mainfrom
remove-outputs-with-tag

Conversation

@timdegroot1996

Copy link
Copy Markdown
Collaborator

Implements #309

Make limit and age respect a tags filter so housekeeping can be scoped to a subset of runs instead of acting globally:

  • limit + tags -> keep the N newest runs carrying any of the given tag(s), remove older matching runs, leave untagged runs untouched.
  • age + tags -> remove only tagged runs within the age range, leave untagged runs untouched (supports both older 10d and younger -10d).

CLI combination syntax uses the regular comma separator, e.g. -r "limit=10,tag=nightly" / -r "age=10d,tag=nightly,tag=prod". arguments.py detects the combination, validates it, and rewrites it into the internal limit=N;tag=... / age=X;tag=... form (the legacy ; form still works). Rules:

  • tags may combine with EITHER limit OR age, not both (error).
  • only one limit/age may be combined with tags (error).
  • a clear INFO line explains exactly what will be deleted; when independent options (index/alias/run_start) are also present the deletion order is printed.

Server /remove-outputs scopes the same way when tags is sent with limit and/or age, and rejects the limit+age+tags combination before removing anything.

Backward compatible: plain limit/age/tag (no partner) behave exactly as before; the elif dispatch in database.py is reordered so combined queries route to the scoped handler.

Docs (CLI + server), OpenAPI examples, and the admin UI labels updated. Also documents the previously under-documented negative-age (-10d) behavior in the docs.

)

Make `limit` and `age` respect a `tags` filter so housekeeping can be
scoped to a subset of runs instead of acting globally:

- limit + tags -> keep the N newest runs carrying any of the given tag(s),
  remove older matching runs, leave untagged runs untouched.
- age + tags -> remove only tagged runs within the age range, leave
  untagged runs untouched (supports both older `10d` and younger `-10d`).

CLI combination syntax uses the regular comma separator, e.g.
`-r "limit=10,tag=nightly"` / `-r "age=10d,tag=nightly,tag=prod"`.
arguments.py detects the combination, validates it, and rewrites it into
the internal `limit=N;tag=...` / `age=X;tag=...` form (the legacy `;`
form still works). Rules:
- tags may combine with EITHER limit OR age, not both (error).
- only one limit/age may be combined with tags (error).
- a clear INFO line explains exactly what will be deleted; when
  independent options (index/alias/run_start) are also present the
  deletion order is printed.

Server `/remove-outputs` scopes the same way when `tags` is sent with
`limit` and/or `age`, and rejects the limit+age+tags combination before
removing anything.

Backward compatible: plain limit/age/tag (no partner) behave exactly as
before; the elif dispatch in database.py is reordered so combined queries
route to the scoped handler.

Docs (CLI + server), OpenAPI examples, and the admin UI labels updated.
Also documents the previously under-documented negative-age (`-10d`)
behavior in the docs.
The branch had grown a parallel "age" + "tags" combination beyond what
#309 asked for. That doubled the combination-detection/validation logic
across arguments.py and server.py, and required database.py to parse a
hand-rolled ';'-encoded suffix in two functions instead of one.

Drop the age+tag combo: age+tags now behaves exactly like on main (two
independent operations). limit+tags is unchanged and still matches the
issue's proposed direction and examples exactly. Updated docs, the CLI
help-text fixture, and tests accordingly.
@timdegroot1996

Copy link
Copy Markdown
Collaborator Author

Pushed a follow-up commit that narrows scope: dropped the `age` + `tags` combination.

This PR's title/description advertise both `limit`+`age` scoping, but issue #309 only asked for `limit`+`tags`. Keeping both meant three places (arguments.py's CLI comma-detection, server.py's JSON-body handling, database.py's dispatch) each had to know about a hand-rolled `limit=N;tag=x`/`age=X;tag=x` mini-DSL, plus a three-way `limit`+`age`+`tags` ambiguity error that only existed because of the extra combination.

What changed:

  • `arguments.py` — `_process_remove_runs` only detects/rewrites `limit`+`tag(s)`; `age`+`tag(s)` now passes through untouched (two independent ops, same as `main`). Dropped the limit+age+tags ambiguity check (exit 3) since it's no longer reachable.
  • `database.py` — `_remove_by_age` reverted to its pre-PR signature (no `run_tags`/tag filtering). `_remove_by_limit` unchanged. Dispatch order: `limit=` still checked before `tag=` (required — the combo string contains the substring `tag=`), `age=` moved back after `tag=` since it no longer needs to precede it.
  • `server.py` — dropped the age-scoping branch and the `limit`+`age`+`tags` rejection guard; `scope_tags` is now `tags and limit` only.
  • Docs (`basic-command-line-interface-cli.md`, `dashboard-server.md`, `admin.html`) and tests trimmed to match — removed the age+tag examples/table rows/test cases, kept everything limit+tag.

Net diff vs. the previous branch state: -138 lines. `limit`+`tags` behavior, examples, and tests are unchanged — this only removes the unrequested `age`+`tags` half. Title/description of this PR should probably be updated to drop the "and age" framing before merge. Full python suite (311 tests) green; CLI help-text fixture updated and reverified.

remove_runs() caught every per-item failure with a bare except:,
discarding the real exception and always printing the same generic
"could not find" message regardless of cause. Now prints the actual
exception.

_remove_by_age had two paths that built a warning/error string but
never printed it, so on the CLI (unlike the server API, which returns
the console string) a no-op removal or an invalid age format produced
zero visible output - indistinguishable from a successful no-op. Both
now print, matching every sibling _remove_by_* method.

Updated the removerun CLI fixture: the alias-not-found line now shows
the real exception detail instead of the old generic wording.
@timdegroot1996
timdegroot1996 merged commit 5bf02ea into main Sep 13, 2026
5 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Improvement] Add retention helper to /remove-outputs: keep N newest runs within a tag

1 participant