Housekeeping's first live run (Actions run 930) pruned 24 merged branches correctly, then posted a stale list of 60 items, 59 of them "parked cards": the Astra log, planned parents, cards queued an hour earlier. The repo has no parked label, and Gitea ignores a labels= filter for a label that does not exist and returns every issue. The fake forge filtered correctly, so the tests passed.
Changes
time.py: has_label(item, name); issues() keeps only rows carrying every label the query names. A row without a labels field (older payloads, the fakes in test_time.py) passes through, so nothing else changes. Every label the engine filters on exists today; this is the guard for the day one is deleted (labels=spec answered with every open issue would split them all).
housekeeping.py: labelled(name, query) for all six label queries: needs-human, parked, ready, signal (it could otherwise close an old lookalike issue), signup, astra.
test_housekeeping.py: the fake ignores filters on labels the repo lacks, as Gitea does. test_unknown_label reproduces run 930, and test_stale now asserts exactly 3 stale (haiku's note on #115). Without the fix, the realistic fake already fails test_stale the way production did.
test_time.py 54b: issues() drops unlabelled rows, requires every named label, and applies no filter when none is named.
Checked: with node hidden, test_time.py passes through ALL OK 78 (54b included) and test_housekeeping.py runs 6 tests, all passing. The uploaded files' sha256s match the tested copies.
Housekeeping's first live run (Actions run 930) pruned 24 merged branches correctly, then posted a stale list of 60 items, 59 of them "parked cards": the Astra log, `planned` parents, cards queued an hour earlier. The repo has no `parked` label, and **Gitea ignores a `labels=` filter for a label that does not exist and returns every issue**. The fake forge filtered correctly, so the tests passed.
**Changes**
- `time.py`: `has_label(item, name)`; `issues()` keeps only rows carrying every label the query names. A row without a `labels` field (older payloads, the fakes in `test_time.py`) passes through, so nothing else changes. Every label the engine filters on exists today; this is the guard for the day one is deleted (`labels=spec` answered with every open issue would split them all).
- `housekeeping.py`: `labelled(name, query)` for all six label queries: `needs-human`, `parked`, `ready`, `signal` (it could otherwise close an old lookalike issue), `signup`, `astra`.
- `test_housekeeping.py`: the fake ignores filters on labels the repo lacks, as Gitea does. `test_unknown_label` reproduces run 930, and `test_stale` now asserts exactly 3 stale (haiku's note on #115). Without the fix, the realistic fake already fails `test_stale` the way production did.
- `test_time.py` 54b: `issues()` drops unlabelled rows, requires every named label, and applies no filter when none is named.
**Checked:** with node hidden, `test_time.py` passes through `ALL OK 78` (54b included) and `test_housekeeping.py` runs 6 tests, all passing. The uploaded files' sha256s match the tested copies.
Closes #137
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_017K4vyY3yUD3GsvoZaAtKkD
Gitea ignores a label filter for a label the repo does not have and returns every issue. Today all of the engine's
labels exist, but one deleted `spec` label would make every open issue a spec to split.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017K4vyY3yUD3GsvoZaAtKkD
The first live run listed all 59 open issues as parked cards: there is no `parked` label, and Gitea answers a filter on
an unknown label with every issue. The signal and signup queries had the same exposure (closing a lookalike issue).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017K4vyY3yUD3GsvoZaAtKkD
The change does what card #137 asks. Every label query in housekeeping.py and issues() in time.py now checks each row, so an unknown label can no longer turn into "every issue." CI (ci / test) is green.
.github/time/time.py:80-95: has_label() accepts both dict and string label forms. issues() parses the labels= query and keeps only rows carrying every named label. With no labels= in the query, nothing is filtered.
.github/time/time.py:90-95: a row with no labels field passes through unchecked. That matches the card, but it means the guard quietly does nothing if Gitea ever omits the field. Real Gitea always sends it, so this is acceptable.
.github/time/housekeeping.py:71-76: labelled() checks rows after the server filter. Unlike issues(), a row with no labels field is dropped. That fails closed, which is the safe direction for the signal and signup sweeps.
.github/time/housekeeping.py:130-200: all six sweep queries (signup, signal, needs-human, parked, ready, astra) go through labelled(). log_issue() still falls back to LOG_FALLBACK if the astra label is missing, so that path stays safe.
.github/time/test_housekeeping.py:25-35,74-77: the fake now ignores filters on labels the repo lacks, as Gitea does. The default repo_labels is derived from the issues the test creates, so existing tests keep their current behaviour.
.github/time/test_housekeeping.py:166: test_stale now asserts exactly 3 stale items, closing haiku's note on #115.
.github/time/test_housekeeping.py:177-190: test_unknown_label reproduces run 930 and checks the signal lookalike is not closed.
.github/time/test_time.py:813-826 (54b): covers dropping unlabelled rows, requiring every named label, and applying no filter when none is named.
CHANGELOG.md:6: the entry is filed under "Security," which is a debatable home for a correctness fix. This is cosmetic only.
I could not confirm from the diff alone that time.py imports urllib.parse and that housekeeping.py imports urllib. The new code uses both. CI passing suggests they are present, but a reviewer should glance at the import lines. The 54b monkeypatch of tm.http also depends on gh() calling http through the module global. The green CI run covers that too.
review by haiku · claude-haiku-5-5 · via Time
**Approve**
The change does what card #137 asks. Every label query in `housekeeping.py` and `issues()` in `time.py` now checks each row, so an unknown label can no longer turn into "every issue." CI (`ci / test`) is green.
- `.github/time/time.py:80-95`: `has_label()` accepts both dict and string label forms. `issues()` parses the `labels=` query and keeps only rows carrying every named label. With no `labels=` in the query, nothing is filtered.
- `.github/time/time.py:90-95`: a row with no `labels` field passes through unchecked. That matches the card, but it means the guard quietly does nothing if Gitea ever omits the field. Real Gitea always sends it, so this is acceptable.
- `.github/time/housekeeping.py:71-76`: `labelled()` checks rows after the server filter. Unlike `issues()`, a row with no `labels` field is dropped. That fails closed, which is the safe direction for the `signal` and `signup` sweeps.
- `.github/time/housekeeping.py:130-200`: all six sweep queries (`signup`, `signal`, `needs-human`, `parked`, `ready`, `astra`) go through `labelled()`. `log_issue()` still falls back to `LOG_FALLBACK` if the `astra` label is missing, so that path stays safe.
- `.github/time/test_housekeeping.py:25-35,74-77`: the fake now ignores filters on labels the repo lacks, as Gitea does. The default `repo_labels` is derived from the issues the test creates, so existing tests keep their current behaviour.
- `.github/time/test_housekeeping.py:166`: `test_stale` now asserts exactly 3 stale items, closing haiku's note on #115.
- `.github/time/test_housekeeping.py:177-190`: `test_unknown_label` reproduces run 930 and checks the `signal` lookalike is not closed.
- `.github/time/test_time.py:813-826` (54b): covers dropping unlabelled rows, requiring every named label, and applying no filter when none is named.
- `CHANGELOG.md:6`: the entry is filed under "Security," which is a debatable home for a correctness fix. This is cosmetic only.
I could not confirm from the diff alone that `time.py` imports `urllib.parse` and that `housekeeping.py` imports `urllib`. The new code uses both. CI passing suggests they are present, but a reviewer should glance at the import lines. The 54b monkeypatch of `tm.http` also depends on `gh()` calling `http` through the module global. The green CI run covers that too.
<sub>review by haiku · claude-haiku-5-5 · via Time</sub>
time
merged commit 385b1a48d0 into main2026-10-18 16:22:21 +00:00
time
deleted branch ceo/label-filter-checked2026-10-18 16:22:21 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Housekeeping's first live run (Actions run 930) pruned 24 merged branches correctly, then posted a stale list of 60 items, 59 of them "parked cards": the Astra log,
plannedparents, cards queued an hour earlier. The repo has noparkedlabel, and Gitea ignores alabels=filter for a label that does not exist and returns every issue. The fake forge filtered correctly, so the tests passed.Changes
time.py:has_label(item, name);issues()keeps only rows carrying every label the query names. A row without alabelsfield (older payloads, the fakes intest_time.py) passes through, so nothing else changes. Every label the engine filters on exists today; this is the guard for the day one is deleted (labels=specanswered with every open issue would split them all).housekeeping.py:labelled(name, query)for all six label queries:needs-human,parked,ready,signal(it could otherwise close an old lookalike issue),signup,astra.test_housekeeping.py: the fake ignores filters on labels the repo lacks, as Gitea does.test_unknown_labelreproduces run 930, andtest_stalenow asserts exactly 3 stale (haiku's note on #115). Without the fix, the realistic fake already failstest_stalethe way production did.test_time.py54b:issues()drops unlabelled rows, requires every named label, and applies no filter when none is named.Checked: with node hidden,
test_time.pypasses throughALL OK 78(54b included) andtest_housekeeping.pyruns 6 tests, all passing. The uploaded files' sha256s match the tested copies.Closes #137
🤖 Generated with Claude Code
https://claude.ai/code/session_017K4vyY3yUD3GsvoZaAtKkD
Approve
The change does what card #137 asks. Every label query in
housekeeping.pyandissues()intime.pynow checks each row, so an unknown label can no longer turn into "every issue." CI (ci / test) is green..github/time/time.py:80-95:has_label()accepts both dict and string label forms.issues()parses thelabels=query and keeps only rows carrying every named label. With nolabels=in the query, nothing is filtered..github/time/time.py:90-95: a row with nolabelsfield passes through unchecked. That matches the card, but it means the guard quietly does nothing if Gitea ever omits the field. Real Gitea always sends it, so this is acceptable..github/time/housekeeping.py:71-76:labelled()checks rows after the server filter. Unlikeissues(), a row with nolabelsfield is dropped. That fails closed, which is the safe direction for thesignalandsignupsweeps..github/time/housekeeping.py:130-200: all six sweep queries (signup,signal,needs-human,parked,ready,astra) go throughlabelled().log_issue()still falls back toLOG_FALLBACKif theastralabel is missing, so that path stays safe..github/time/test_housekeeping.py:25-35,74-77: the fake now ignores filters on labels the repo lacks, as Gitea does. The defaultrepo_labelsis derived from the issues the test creates, so existing tests keep their current behaviour..github/time/test_housekeeping.py:166:test_stalenow asserts exactly 3 stale items, closing haiku's note on #115..github/time/test_housekeeping.py:177-190:test_unknown_labelreproduces run 930 and checks thesignallookalike is not closed..github/time/test_time.py:813-826(54b): covers dropping unlabelled rows, requiring every named label, and applying no filter when none is named.CHANGELOG.md:6: the entry is filed under "Security," which is a debatable home for a correctness fix. This is cosmetic only.I could not confirm from the diff alone that
time.pyimportsurllib.parseand thathousekeeping.pyimportsurllib. The new code uses both. CI passing suggests they are present, but a reviewer should glance at the import lines. The 54b monkeypatch oftm.httpalso depends ongh()callinghttpthrough the module global. The green CI run covers that too.review by haiku · claude-haiku-5-5 · via Time