#ai-generated
Problem
Die Provider-Abstraktion leakt die Fehler-Semantik. Jeder Provider wirft eine eigene, voneinander unabhängige Exception:
GitHubError(RuntimeError) — providers/github.py, .status: int, .message
GitLabError(RuntimeError) — providers/gitlab.py, .status, .message
AzureDevOpsError(RuntimeError) — providers/azuredevops.py, .status
Es gibt keinen gemeinsamen Basistyp in providers/base.py (dort nur Relation-Exceptions) und keinen dedizierten Rate-Limit-Typ. Folgen für Consumer (workboard, agent-project-issues):
except <ProviderError> ist nicht möglich — man muss alle drei konkreten Typen importieren oder breit except Exception fangen.
- „War das ein Rate-Limit und wann darf ich wieder?" lässt sich nur per
.status == 403/429 plus String-Schnüffeln in .message (GitHub) beantworten — pro Provider unterschiedlich. Genau das Detail, das die Abstraktion kapseln sollte, liegt beim Consumer.
Konkret hat das im Workboard dazu geführt, dass die Rate-Limit-Behandlung toter Code war (sie fing httpx.HTTPStatusError, das real kein Provider wirft) und das Board bei Rate-Limit still „Keine offenen Tickets" zeigte. Siehe workboard-Ticket (Relation).
Vorschlag
In providers/base.py:
class ProviderError(RuntimeError):
def __init__(self, status: int, message: str):
super().__init__(message)
self.status = status
self.message = message
class RateLimitError(ProviderError):
def __init__(self, status: int, message: str, retry_after: int | None = None):
super().__init__(status, message)
self.retry_after = retry_after # Sekunden bis Reset, falls bekannt
GitHubError / GitLabError / AzureDevOpsError erben von ProviderError (Signatur/.status/.message bleiben → rückwärtskompatibel, bestehende except GitHubError funktionieren weiter).
- Jeder
_check() wirft RateLimitError statt des generischen Provider-Errors, wenn er ein Rate-Limit erkennt, und füllt retry_after:
- GitHub:
403 + Header x-ratelimit-remaining == "0" → retry_after = x-ratelimit-reset - now (Sekunden). (Logik existiert bereits halb in github.py::_check, nur als Message-Präfix.)
- GitLab:
429 → Retry-After bzw. RateLimit-Reset.
- Azure:
429 → Retry-After.
Akzeptanzkriterien
ProviderError + RateLimitError in base.py, die drei Provider-Errors erben davon.
- Pro Provider ein Test: Rate-Limit-Response →
RateLimitError mit korrektem retry_after; gewöhnlicher Fehler → konkreter ProviderError-Subtyp mit .status.
RateLimitError ist auch ein ProviderError und (für Kompat) weiterhin ein RuntimeError.
- Export über
providers/__init__.py bzw. Package-Top-Level, sodass Consumer from lib_python_projects.providers.base import ProviderError, RateLimitError nutzen können.
Downstream (eigene Tickets)
- workboard konsumiert
RateLimitError/ProviderError statt httpx.HTTPStatusError + .status-Sniffing.
- agent-project-issues — vermutlich No-op (surfacet Fehler schon via
_safe), Verify-Ticket.
Optional als Folge-Ticket: ETag/If-None-Match-Conditional-Requests im Provider-Layer (GitHub 304 Not Modified zählt nicht gegen das Primär-Rate-Limit) — größter Hebel für pollende Consumer auf einem geteilten Token.
#ai-generated
Problem
Die Provider-Abstraktion leakt die Fehler-Semantik. Jeder Provider wirft eine eigene, voneinander unabhängige Exception:
GitHubError(RuntimeError)—providers/github.py,.status: int,.messageGitLabError(RuntimeError)—providers/gitlab.py,.status,.messageAzureDevOpsError(RuntimeError)—providers/azuredevops.py,.statusEs gibt keinen gemeinsamen Basistyp in
providers/base.py(dort nur Relation-Exceptions) und keinen dedizierten Rate-Limit-Typ. Folgen für Consumer (workboard, agent-project-issues):except <ProviderError>ist nicht möglich — man muss alle drei konkreten Typen importieren oder breitexcept Exceptionfangen..status == 403/429plus String-Schnüffeln in.message(GitHub) beantworten — pro Provider unterschiedlich. Genau das Detail, das die Abstraktion kapseln sollte, liegt beim Consumer.Konkret hat das im Workboard dazu geführt, dass die Rate-Limit-Behandlung toter Code war (sie fing
httpx.HTTPStatusError, das real kein Provider wirft) und das Board bei Rate-Limit still „Keine offenen Tickets" zeigte. Siehe workboard-Ticket (Relation).Vorschlag
In
providers/base.py:GitHubError/GitLabError/AzureDevOpsErrorerben vonProviderError(Signatur/.status/.messagebleiben → rückwärtskompatibel, bestehendeexcept GitHubErrorfunktionieren weiter)._check()wirftRateLimitErrorstatt des generischen Provider-Errors, wenn er ein Rate-Limit erkennt, und fülltretry_after:403+ Headerx-ratelimit-remaining == "0"→retry_after = x-ratelimit-reset - now(Sekunden). (Logik existiert bereits halb ingithub.py::_check, nur als Message-Präfix.)429→Retry-Afterbzw.RateLimit-Reset.429→Retry-After.Akzeptanzkriterien
ProviderError+RateLimitErrorinbase.py, die drei Provider-Errors erben davon.RateLimitErrormit korrektemretry_after; gewöhnlicher Fehler → konkreterProviderError-Subtyp mit.status.RateLimitErrorist auch einProviderErrorund (für Kompat) weiterhin einRuntimeError.providers/__init__.pybzw. Package-Top-Level, sodass Consumerfrom lib_python_projects.providers.base import ProviderError, RateLimitErrornutzen können.Downstream (eigene Tickets)
RateLimitError/ProviderErrorstatthttpx.HTTPStatusError+.status-Sniffing._safe), Verify-Ticket.Optional als Folge-Ticket: ETag/
If-None-Match-Conditional-Requests im Provider-Layer (GitHub304 Not Modifiedzählt nicht gegen das Primär-Rate-Limit) — größter Hebel für pollende Consumer auf einem geteilten Token.