search_issues_cross_repo leaks repo context on a mid-loop exception #21

Open
opened 2026-10-01 16:40:37 +02:00 by kade · 0 comments
Owner

search_issues_cross_repo temporarily mutates shared client state
(config.repo_owner / config.repo_name) to search each repo, and restored it
inside the try block:

# before
self.config.repo_owner = owner
self.config.repo_name = name
issues = self.issues.get_issues()       # if this raises...
...
self.config.repo_owner = original_owner # ...restore is skipped -> leak

An exception between the assignment and the restore leaves the last-visited repo
permanently installed on the client, so every subsequent call on that client
targets the wrong repository.

Fix (implemented in the working tree)

Restore in a finally, and narrow the blanket catch to the exceptions that can
actually occur (request failures + malformed repo reference):

try:
    self.config.repo_owner = owner
    self.config.repo_name = name
    ...
except (requests.RequestException, ValueError, KeyError, TypeError) as e:
    print(f"Error searching {repo_full_name}: {e}")
finally:
    self.config.repo_owner = original_owner
    self.config.repo_name = original_name

Tests (tests/test_labels.py, new)

  • test_repo_context_survives_a_failing_repo -- asserts repo_owner/repo_name
    are unchanged after the first repo raises and the second succeeds. Fails on HEAD.
`search_issues_cross_repo` temporarily mutates shared client state (`config.repo_owner` / `config.repo_name`) to search each repo, and restored it *inside* the `try` block: ```python # before self.config.repo_owner = owner self.config.repo_name = name issues = self.issues.get_issues() # if this raises... ... self.config.repo_owner = original_owner # ...restore is skipped -> leak ``` An exception between the assignment and the restore leaves the last-visited repo permanently installed on the client, so every subsequent call on that client targets the wrong repository. ## Fix (implemented in the working tree) Restore in a `finally`, and narrow the blanket catch to the exceptions that can actually occur (request failures + malformed repo reference): ```python try: self.config.repo_owner = owner self.config.repo_name = name ... except (requests.RequestException, ValueError, KeyError, TypeError) as e: print(f"Error searching {repo_full_name}: {e}") finally: self.config.repo_owner = original_owner self.config.repo_name = original_name ``` ## Tests (tests/test_labels.py, new) - `test_repo_context_survives_a_failing_repo` -- asserts `repo_owner`/`repo_name` are unchanged after the first repo raises and the second succeeds. Fails on HEAD.
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#21
No description provided.