forgejo-cli issues list serves a stale mappy cache; use_cache=False is dropped, and create_issue duplicate-checks against that same stale list #17

Closed
opened 2026-09-30 17:51:03 +02:00 by kade · 0 comments
Owner

Summary

forgejo-cli issues list serves a stale mappy-cached snapshot and silently omits issues created
since the cache entry was written. use_cache=False is accepted by
search_issues_with_reranking and then dropped on the floor before the actual HTTP request.

The same defect makes create_issue's duplicate-title check read a snapshot that predates the
issues it is meant to be guarding against.

Where

src/forgejo_client/cli.py:176

def search_issues_with_reranking(self, owner, repo, query, state, use_ordinator=False, use_cache=False):
    ...
    if not use_cache and self.cache:          # guards only the search-level mappy entry
        ...
    client = self.get_client(owner, repo)
    issues = client.issues.get_issues(state=state)     # <-- use_cache never forwarded

get_issues has no cache parameter at all and calls _make_request(..., params=params) with the
default use_cache=True (src/forgejo_client/modules/issues.py:16-33).

Reproduction

Same request, same moment, differing only in the cache flag:

A) client.issues.get_issues(state='open')                     -> 5   (default, cached)
B) _make_request('/repos/kade/r/issues', params={'state':'open'}, use_cache=False) -> 6
C) _make_request('/repos/kade/r/issues', params={'state':'open'}, use_cache=True)  -> 5

Reproduced on kade/r immediately after creating issue #6. get_issue(6) reports state: open
and the body, label and title are all correct — only the list is stale. There is no pagination
involvement: ?state=open, ?state=open&limit=30 and ?state=open&limit=50 all return 6.

The cache key is (method, endpoint, str(params)), so the stale entry is per-parameter-set and
persists until the mappy TTL expires.

Why it matters beyond the listing

create_issue opens with a duplicate-title guard (modules/issues.py:51):

existing_issues = self.get_issues(state="all")
for issue in existing_issues:
    if issue.get('title') == title:

That call is cached too. The duplicate check cannot see issues created within the cache TTL, so
it does not prevent the thing it exists to prevent — it only prevents duplicates of issues older
than the cache entry. The safety check is reading a snapshot from before the thing it is checking.

A second consumer at client.py:209 and four more in modules/issues.py
(:298, :392, :434, :482) and search/search_processor.py:196 inherit the same behaviour.

Suggested fix

  1. Thread the flag through. Give get_issues a use_cache: bool = True parameter and forward
    it to _make_request. Then pass use_cache from search_issues_with_reranking instead of
    dropping it.
  2. create_issue's duplicate check must not be cached. It is a correctness guard, not a
    read-through cache candidate — the request it makes is precisely the one where staleness causes
    the bug. It should be use_cache=False unconditionally.
  3. Consider a --no-cache on issues list. Even with (1), a cached list is a footgun for
    anyone verifying that an action just took effect — which is the exact moment the cache is least
    helpful.

Notes

  • Found while filing kade/r#6. The immediate symptom — "I created an issue and the list does not
    show it" — reads exactly like the contribution gate auto-closing a new issue, which sent the
    investigation down the wrong path for a few minutes.
  • _make_request already treats GET as the only cacheable method, so scoping the fix to
    get_issues is sufficient; no other method is affected.
  • No code change proposed here — this is a report.
## Summary `forgejo-cli issues list` serves a stale mappy-cached snapshot and silently omits issues created since the cache entry was written. `use_cache=False` is accepted by `search_issues_with_reranking` and then dropped on the floor before the actual HTTP request. The same defect makes `create_issue`'s duplicate-title check read a snapshot that predates the issues it is meant to be guarding against. ## Where `src/forgejo_client/cli.py:176` ```python def search_issues_with_reranking(self, owner, repo, query, state, use_ordinator=False, use_cache=False): ... if not use_cache and self.cache: # guards only the search-level mappy entry ... client = self.get_client(owner, repo) issues = client.issues.get_issues(state=state) # <-- use_cache never forwarded ``` `get_issues` has no cache parameter at all and calls `_make_request(..., params=params)` with the default `use_cache=True` (`src/forgejo_client/modules/issues.py:16-33`). ## Reproduction Same request, same moment, differing only in the cache flag: ``` A) client.issues.get_issues(state='open') -> 5 (default, cached) B) _make_request('/repos/kade/r/issues', params={'state':'open'}, use_cache=False) -> 6 C) _make_request('/repos/kade/r/issues', params={'state':'open'}, use_cache=True) -> 5 ``` Reproduced on `kade/r` immediately after creating issue #6. `get_issue(6)` reports `state: open` and the body, label and title are all correct — only the *list* is stale. There is no pagination involvement: `?state=open`, `?state=open&limit=30` and `?state=open&limit=50` all return 6. The cache key is `(method, endpoint, str(params))`, so the stale entry is per-parameter-set and persists until the mappy TTL expires. ## Why it matters beyond the listing `create_issue` opens with a duplicate-title guard (`modules/issues.py:51`): ```python existing_issues = self.get_issues(state="all") for issue in existing_issues: if issue.get('title') == title: ``` That call is cached too. **The duplicate check cannot see issues created within the cache TTL**, so it does not prevent the thing it exists to prevent — it only prevents duplicates of issues older than the cache entry. The safety check is reading a snapshot from before the thing it is checking. A second consumer at `client.py:209` and four more in `modules/issues.py` (`:298`, `:392`, `:434`, `:482`) and `search/search_processor.py:196` inherit the same behaviour. ## Suggested fix 1. **Thread the flag through.** Give `get_issues` a `use_cache: bool = True` parameter and forward it to `_make_request`. Then pass `use_cache` from `search_issues_with_reranking` instead of dropping it. 2. **`create_issue`'s duplicate check must not be cached.** It is a correctness guard, not a read-through cache candidate — the request it makes is precisely the one where staleness causes the bug. It should be `use_cache=False` unconditionally. 3. **Consider a `--no-cache` on `issues list`.** Even with (1), a cached list is a footgun for anyone verifying that an action just took effect — which is the exact moment the cache is least helpful. ## Notes - Found while filing `kade/r#6`. The immediate symptom — "I created an issue and the list does not show it" — reads exactly like the contribution gate auto-closing a new issue, which sent the investigation down the wrong path for a few minutes. - `_make_request` already treats `GET` as the only cacheable method, so scoping the fix to `get_issues` is sufficient; no other method is affected. - No code change proposed here — this is a report.
kade closed this issue 2026-10-03 11:01:16 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kade/forgejo-client#17
No description provided.