Fix #19 #20: Teil-Abrufe verschlucken ihre Fehler nicht mehr #21

Merged
florian.wilde merged 1 commits from fix/19-abrufe-nicht-verschlucken into main 2026-09-09 20:05:27 +00:00
Owner

getJsonSafe machte aus jedem Fehlschlag eine leere Liste. Damit war ein 403 nicht mehr von „das Repository hat keine Branches" zu unterscheiden: Der Abgleich lief durch, meldete „Snapshot aktualisiert: 0 Branches, 0 Tags", setzte kein syncError — und überschrieb dabei den letzten guten Stand mit Nullen.

Ein Repository mit 65 Commits sah danach aus wie ein leeres, und die Ursache war nicht zu ermitteln, weil genau die Auskunft, die sie benannt hätte, weggeworfen worden war.

Was sich ändert

Teilabrufe sammelt je Feld, was schiefging — mit HTTP-Status, denn daran hängt die Ursache: 403 heißt Scopes, 429 Rate-Limit, 408 Zeitüberschreitung, 0 nicht erreicht. Der Fallback bleibt; ein einzelner Fehlschlag darf den ganzen Abgleich nicht abbrechen.

persistSnapshot lässt stehen, was nicht geholt werden konnte. Der Snapshot ist ein Zwischenspeicher — ein alter Stand mit sichtbarem Datum ist immer brauchbarer als eine leere Liste, die aussieht wie eine Aussage. Die abgeleiteten Felder gehen mit ihrer Liste: lastCommit* hängt an commits, latestTag an tags und releases, sonst wären sie in sich widersprüchlich.

Sichtbar an drei Stellen: in der Meldung („Teilweise aktualisiert – nicht geholt: …"), als Hinweis am Repository auf der Projektseite mit Grund je Feld, und in der Datenbank als missing samt completeAt — wann der Stand zuletzt vollständig war.

Bewusst kein Fehlschlag. syncError bleibt leer, der Zähler fürs Zurückhalten steigt nicht. Der Rest des Standes ist ja frisch, und ein Repository deswegen stundenlang auszusetzen wäre die falsche Antwort.

GIT_API_TIMEOUT_MS wird geprüft. Number("") ist 0, Number("abc") ist NaN; setTimeout löst bei beidem sofort aus, und dann bricht jeder Aufruf ab, bevor er beginnt. Eine verrutschte Zeile in der .env legte damit den ganzen Abgleich still — lautlos, versteht sich. Unter 1000 ms oder unlesbar gilt jetzt die Vorgabe, mit Warnung im Log.

Nachgewiesen, nicht behauptet

Gegen einen nachgebauten Gitea-Server, der genau den beobachteten Fall erzeugt — Basis-Aufruf antwortet, Teillisten geben 403 — und eine Wegwerf-Datenbank:

branchCount tagCount commits releases missing completeAt
gesund 2 3 2 1 {} 18:03
Teillisten 403 2 3 2 1 8 Felder, HTTP 403 18:03 (unverändert)
wieder gesund 2 3 2 1 {} 18:04

Nichts verloren, fetchedAt läuft trotzdem mit, und nach dem nächsten gesunden Durchlauf räumt sich der Hinweis von selbst weg.

Geprüft

  • tsc sauber · 289 Unit-Tests (8 neue zu Teilabrufe und zeitlimit) · npm run build durch
  • Der Hinweis auf der Projektseite angesehen: nennt alle acht Felder mit Grund und das Datum des letzten vollständigen Standes
  • axe-core 0 Verstöße auf 1280 px und 375 px, kein seitlicher Überlauf

Migration

20260901160000_snapshot_teilabrufe — zwei Spalten an RepoSnapshot (missing, completeAt). Bestehende Snapshots bekommen missing = {} und completeAt = NULL; letzteres ist ehrlich, denn von vergangenen Durchläufen wissen wir nicht, ob sie vollständig waren.

Was das nicht tut

Es findet nicht die Ursache der Ausfälle auf deiner Instanz. Es macht sie sichtbar: nach dem nächsten Abgleich steht der HTTP-Status auf der Projektseite, und damit wissen wir, ob es Scopes, Rate-Limit oder Zeitüberschreitung sind. Bis dahin verlierst du beim Suchen wenigstens keine Daten mehr.

Closes #19
Closes #20

`getJsonSafe` machte aus jedem Fehlschlag eine leere Liste. Damit war ein 403 nicht mehr von „das Repository hat keine Branches" zu unterscheiden: Der Abgleich lief durch, meldete „Snapshot aktualisiert: 0 Branches, 0 Tags", setzte kein `syncError` — und überschrieb dabei den letzten guten Stand mit Nullen. Ein Repository mit 65 Commits sah danach aus wie ein leeres, und die Ursache war nicht zu ermitteln, weil genau die Auskunft, die sie benannt hätte, weggeworfen worden war. ## Was sich ändert **`Teilabrufe`** sammelt je Feld, was schiefging — mit HTTP-Status, denn daran hängt die Ursache: 403 heißt Scopes, 429 Rate-Limit, 408 Zeitüberschreitung, 0 nicht erreicht. Der Fallback bleibt; ein einzelner Fehlschlag darf den ganzen Abgleich nicht abbrechen. **`persistSnapshot` lässt stehen, was nicht geholt werden konnte.** Der Snapshot ist ein Zwischenspeicher — ein alter Stand mit sichtbarem Datum ist immer brauchbarer als eine leere Liste, die aussieht wie eine Aussage. Die abgeleiteten Felder gehen mit ihrer Liste: `lastCommit*` hängt an `commits`, `latestTag` an `tags` **und** `releases`, sonst wären sie in sich widersprüchlich. **Sichtbar an drei Stellen:** in der Meldung („Teilweise aktualisiert – nicht geholt: …"), als Hinweis am Repository auf der Projektseite mit Grund je Feld, und in der Datenbank als `missing` samt `completeAt` — wann der Stand zuletzt vollständig war. **Bewusst kein Fehlschlag.** `syncError` bleibt leer, der Zähler fürs Zurückhalten steigt nicht. Der Rest des Standes ist ja frisch, und ein Repository deswegen stundenlang auszusetzen wäre die falsche Antwort. **`GIT_API_TIMEOUT_MS` wird geprüft.** `Number("")` ist `0`, `Number("abc")` ist `NaN`; `setTimeout` löst bei beidem sofort aus, und dann bricht jeder Aufruf ab, bevor er beginnt. Eine verrutschte Zeile in der `.env` legte damit den ganzen Abgleich still — lautlos, versteht sich. Unter 1000 ms oder unlesbar gilt jetzt die Vorgabe, mit Warnung im Log. ## Nachgewiesen, nicht behauptet Gegen einen nachgebauten Gitea-Server, der genau den beobachteten Fall erzeugt — Basis-Aufruf antwortet, Teillisten geben 403 — und eine Wegwerf-Datenbank: | | branchCount | tagCount | commits | releases | missing | completeAt | | --- | --- | --- | --- | --- | --- | --- | | gesund | 2 | 3 | 2 | 1 | `{}` | 18:03 | | **Teillisten 403** | **2** | **3** | **2** | **1** | 8 Felder, HTTP 403 | **18:03** (unverändert) | | wieder gesund | 2 | 3 | 2 | 1 | `{}` | 18:04 | Nichts verloren, `fetchedAt` läuft trotzdem mit, und nach dem nächsten gesunden Durchlauf räumt sich der Hinweis von selbst weg. ## Geprüft - `tsc` sauber · **289 Unit-Tests** (8 neue zu `Teilabrufe` und `zeitlimit`) · `npm run build` durch - Der Hinweis auf der Projektseite angesehen: nennt alle acht Felder mit Grund und das Datum des letzten vollständigen Standes - **axe-core 0 Verstöße** auf 1280 px und 375 px, kein seitlicher Überlauf ## Migration `20260901160000_snapshot_teilabrufe` — zwei Spalten an `RepoSnapshot` (`missing`, `completeAt`). Bestehende Snapshots bekommen `missing = {}` und `completeAt = NULL`; letzteres ist ehrlich, denn von vergangenen Durchläufen wissen wir nicht, ob sie vollständig waren. ## Was das *nicht* tut Es findet nicht die Ursache der Ausfälle auf deiner Instanz. Es macht sie sichtbar: nach dem nächsten Abgleich steht der HTTP-Status auf der Projektseite, und damit wissen wir, ob es Scopes, Rate-Limit oder Zeitüberschreitung sind. Bis dahin verlierst du beim Suchen wenigstens keine Daten mehr. Closes #19 Closes #20
florian.wilde added 1 commit 2026-09-01 16:09:51 +00:00
fix(sync): Teil-Abrufe verschlucken ihre Fehler nicht mehr
CI / pruefen (pull_request) Canceled after 0s
b062eb36a1
Behebt florian.wilde/nebula#19 und florian.wilde/nebula#20.

getJsonSafe machte aus jedem Fehlschlag eine leere Liste. Damit war ein
403 nicht mehr von "das Repository hat keine Branches" zu unterscheiden:
Der Abgleich lief durch, meldete "Snapshot aktualisiert: 0 Branches,
0 Tags", setzte kein syncError - und ueberschrieb dabei den letzten guten
Stand mit Nullen. Ein Repository mit 65 Commits sah danach aus wie ein
leeres, und die Ursache war nicht zu ermitteln, weil die Auskunft, die
sie benannt haette, gerade weggeworfen worden war.

Teilabrufe sammelt jetzt je Feld, was schiefging - mit HTTP-Status, denn
daran haengt die Ursache: 403 heisst Scopes, 429 Rate-Limit, 408
Zeitueberschreitung, 0 nicht erreicht. Der Fallback bleibt, ein einzelner
Fehlschlag darf den ganzen Abgleich nicht abbrechen.

Daraus folgt der zweite Teil: persistSnapshot laesst stehen, was nicht
geholt werden konnte. Der Snapshot ist ein Zwischenspeicher - ein alter
Stand mit sichtbarem Datum ist immer brauchbarer als eine leere Liste,
die aussieht wie eine Aussage. Die abgeleiteten Felder gehen mit ihrer
Liste: lastCommit* haengt an commits, latestTag an tags UND releases,
sonst waeren sie in sich widerspruechlich.

Bewusst kein Fehlschlag: syncError bleibt leer und der Zaehler fuers
Zurueckhalten steigt nicht. Der Rest des Standes ist frisch, und ein
Repository deswegen stundenlang auszusetzen waere die falsche Antwort.

GIT_API_TIMEOUT_MS wird geprueft. Number("") ist 0 und Number("abc") ist
NaN; setTimeout loest bei beidem sofort aus, und dann bricht jeder Aufruf
ab, bevor er beginnt. Eine verrutschte Zeile in der .env legte damit den
ganzen Abgleich still - lautlos, versteht sich.

NACHGEWIESEN, NICHT BEHAUPTET

Gegen einen nachgebauten Gitea-Server, der genau den beobachteten Fall
erzeugt - Basis-Aufruf antwortet, Teillisten geben 403 - und eine
Wegwerf-Datenbank:

  gesund      branchCount=2 tagCount=3 commits=2 releases=1 missing={}
  kaputt      branchCount=2 tagCount=3 commits=2 releases=1
              missing={8 Felder mit HTTP 403}, completeAt unveraendert
  wieder gut  missing={} , completeAt fortgeschrieben

Nichts verloren, die Luecke steht in der Meldung und am Repository, und
nach dem naechsten gesunden Durchlauf raeumt sie sich von selbst weg.
axe-core 0 Verstoesse auf 1280 und 375 px, kein seitlicher Ueberlauf.

Der eigentliche Grund der Ausfaelle auf der Instanz ist damit noch nicht
gefunden - aber ab jetzt steht er auf der Projektseite.

Refs #19
Refs #20
florian.wilde merged commit 97ba863356 into main 2026-09-09 20:05:27 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: florian.wilde/nebula#21