Fehlgeschlagene Teil-Abrufe verschwinden lautlos #19

Closed
opened 2026-09-01 15:49:37 +00:00 by florian.wilde · 3 comments
Owner

Der Abgleich holt je Repository acht Listen parallel (Branches, Tags, Commits, Releases, Sprachen, PRs, Issues, CI). Alle acht laufen über getJsonSafe in lib/git/http.ts:

try {
  const result = await getJson<T>(url, options);
  return (result ?? fallback) as T;
} catch {
  return fallback;      // 403, 429, Timeout, DNS – alles wird zu []
}

Ein Fehlschlag ist danach nicht mehr von „das Repository hat keine Branches" zu unterscheiden. Der Abgleich läuft weiter, schreibt Nullen in den Snapshot, setzt kein syncError und meldet Erfolg — die Meldung lautet dann wörtlich „Snapshot aktualisiert: 0 Branches, 0 Tags."

Beobachtet

syslog-platform zeigt 0 Branches statt 1 und keine Commits statt 65, betreuungs-cockpit 0 Tags statt 22 und 0 Releases statt 22. Ältere Repositories, die seitdem nicht abgeglichen wurden, tragen ihre Daten noch.

Was bereits ausgeschlossen ist

  • Die Endpunkte. Acht parallele Aufrufe gegen git.wilde-server.de mit genau den URLs aus fetchRepo: alle 200, zusammen 1030 ms, parallel sogar schneller als nacheinander. Branches 1, Tags 22, Commits 50, Releases 10.
  • Rate-Limit und Nebenläufigkeit. Siehe oben.
  • Eine Code-Regression im Abrufpfad. gitea.ts, http.ts und security/ssrf.ts sind seit v1.9.0 beziehungsweise v1.7.0 unverändert; der letzte Commit dort fügt neun Zeilen getIssueState hinzu, ohne fetchRepo zu berühren.
  • Token, Host, TLS und SSRF-Prüfung im Grundsatz. syslog-platform ist privat, und der Basis-Aufruf liefert weiterhin Name, Sichtbarkeit und Größe. Ohne gültiges Token käme diese Zeile nicht zustande, und getJson hätte geworfen.

Die Ursache liegt damit in der laufenden Instanz — und lässt sich nicht feststellen, weil genau die Auskunft verworfen wird, die sie benennen würde.

Zu tun

  1. Je Teil-Abruf festhalten, ob er gescheitert ist und woran (Status und Meldung), statt ihn in eine leere Liste zu verwandeln.
  2. Das am Repository sichtbar machen — ein Repository, dessen Listen nicht kamen, darf nicht aussehen wie eines, das keine hat.
  3. Die Erfolgsmeldung darf nicht „Snapshot aktualisiert" heißen, wenn die Hälfte der Abrufe fehlgeschlagen ist.
  4. GIT_API_TIMEOUT_MS validieren: Number(process.env.GIT_API_TIMEOUT_MS ?? 15000) in http.ts:4 prüft nichts. Ein leerer oder unlesbarer Wert ergibt 0 bzw. NaN, und setTimeout bricht damit jeden Aufruf sofort ab — eine kaputte Zeile in der .env legt den ganzen Abgleich still, ohne dass irgendwo etwas steht.

Hängt mit dem Verlust des letzten guten Standes zusammen; der ist getrennt erfasst.

Der Abgleich holt je Repository acht Listen parallel (Branches, Tags, Commits, Releases, Sprachen, PRs, Issues, CI). Alle acht laufen über `getJsonSafe` in `lib/git/http.ts`: ```ts try { const result = await getJson<T>(url, options); return (result ?? fallback) as T; } catch { return fallback; // 403, 429, Timeout, DNS – alles wird zu [] } ``` Ein Fehlschlag ist danach nicht mehr von „das Repository hat keine Branches" zu unterscheiden. Der Abgleich läuft weiter, schreibt Nullen in den Snapshot, setzt **kein** `syncError` und meldet Erfolg — die Meldung lautet dann wörtlich „Snapshot aktualisiert: 0 Branches, 0 Tags." ## Beobachtet `syslog-platform` zeigt 0 Branches statt 1 und keine Commits statt 65, `betreuungs-cockpit` 0 Tags statt 22 und 0 Releases statt 22. Ältere Repositories, die seitdem nicht abgeglichen wurden, tragen ihre Daten noch. ## Was bereits ausgeschlossen ist - **Die Endpunkte.** Acht parallele Aufrufe gegen `git.wilde-server.de` mit genau den URLs aus `fetchRepo`: alle 200, zusammen 1030 ms, parallel sogar schneller als nacheinander. Branches 1, Tags 22, Commits 50, Releases 10. - **Rate-Limit und Nebenläufigkeit.** Siehe oben. - **Eine Code-Regression im Abrufpfad.** `gitea.ts`, `http.ts` und `security/ssrf.ts` sind seit v1.9.0 beziehungsweise v1.7.0 unverändert; der letzte Commit dort fügt neun Zeilen `getIssueState` hinzu, ohne `fetchRepo` zu berühren. - **Token, Host, TLS und SSRF-Prüfung im Grundsatz.** `syslog-platform` ist privat, und der Basis-Aufruf liefert weiterhin Name, Sichtbarkeit und Größe. Ohne gültiges Token käme diese Zeile nicht zustande, und `getJson` hätte geworfen. Die Ursache liegt damit in der laufenden Instanz — und lässt sich nicht feststellen, weil genau die Auskunft verworfen wird, die sie benennen würde. ## Zu tun 1. Je Teil-Abruf festhalten, ob er gescheitert ist und woran (Status und Meldung), statt ihn in eine leere Liste zu verwandeln. 2. Das am Repository sichtbar machen — ein Repository, dessen Listen nicht kamen, darf nicht aussehen wie eines, das keine hat. 3. Die Erfolgsmeldung darf nicht „Snapshot aktualisiert" heißen, wenn die Hälfte der Abrufe fehlgeschlagen ist. 4. `GIT_API_TIMEOUT_MS` validieren: `Number(process.env.GIT_API_TIMEOUT_MS ?? 15000)` in `http.ts:4` prüft nichts. Ein leerer oder unlesbarer Wert ergibt `0` bzw. `NaN`, und `setTimeout` bricht damit jeden Aufruf sofort ab — eine kaputte Zeile in der `.env` legt den ganzen Abgleich still, ohne dass irgendwo etwas steht. Hängt mit dem Verlust des letzten guten Standes zusammen; der ist getrennt erfasst.
florian.wilde added the in-arbeit label 2026-09-01 15:50:20 +00:00
florian.wilde started working 2026-09-01 15:50:36 +00:00
Author
Owner

🤖 Claude Code hat mit der Umsetzung begonnen.

  • Branch: fix/19-abrufe-nicht-verschlucken
  • Verstanden als: Ein gescheiterter Teil-Abruf darf nicht als leere Liste durchgehen — er muss festgehalten, angezeigt und in der Erfolgsmeldung genannt werden.
  • Zusammen mit #20 auf einem Branch. #20 verlangt die Unterscheidung „leer geholt“ gegen „nicht geholt“, und die entsteht genau hier. Getrennt umgesetzt wäre der erste PR eine halbe Sache. Der PR schließt beide; die Zeit läuft auf diesem Issue.
  • Geplantes Vorgehen:
    • Die acht Teil-Abrufe sammeln ihre Fehlschläge statt sie zu verschlucken — Feldname, HTTP-Status und Meldung
    • Das Ergebnis wandert in den Snapshot und von dort an die Projektseite: welcher Teil fehlt, woran es lag, und von wann der angezeigte Stand ist
    • persistSnapshot lässt eine Liste stehen, die nicht geholt werden konnte (#20)
    • GIT_API_TIMEOUT_MS wird validiert; Number("") ergibt heute 0 und bricht jeden Aufruf sofort ab
    • Nachgesehen statt angenommen: ein Repository, dessen Teil-Abrufe absichtlich scheitern, gegen eine Wegwerf-Datenbank

Der eigentliche Fehler ist damit noch nicht gefunden — diese Änderung macht ihn sichtbar. Sobald sie läuft, sagt dir die Projektseite, welcher Status zurückkommt; dann wissen wir, ob es Scopes, Timeout oder etwas Drittes ist.

Zeiterfassung läuft.

🤖 **Claude Code hat mit der Umsetzung begonnen.** - Branch: `fix/19-abrufe-nicht-verschlucken` - Verstanden als: Ein gescheiterter Teil-Abruf darf nicht als leere Liste durchgehen — er muss festgehalten, angezeigt und in der Erfolgsmeldung genannt werden. - **Zusammen mit #20 auf einem Branch.** #20 verlangt die Unterscheidung „leer geholt“ gegen „nicht geholt“, und die entsteht genau hier. Getrennt umgesetzt wäre der erste PR eine halbe Sache. Der PR schließt beide; die Zeit läuft auf diesem Issue. - Geplantes Vorgehen: - Die acht Teil-Abrufe sammeln ihre Fehlschläge statt sie zu verschlucken — Feldname, HTTP-Status und Meldung - Das Ergebnis wandert in den Snapshot und von dort an die Projektseite: welcher Teil fehlt, woran es lag, und von wann der angezeigte Stand ist - `persistSnapshot` lässt eine Liste stehen, die nicht geholt werden konnte (#20) - `GIT_API_TIMEOUT_MS` wird validiert; `Number("")` ergibt heute `0` und bricht jeden Aufruf sofort ab - Nachgesehen statt angenommen: ein Repository, dessen Teil-Abrufe absichtlich scheitern, gegen eine Wegwerf-Datenbank **Der eigentliche Fehler ist damit noch nicht gefunden** — diese Änderung macht ihn sichtbar. Sobald sie läuft, sagt dir die Projektseite, welcher Status zurückkommt; dann wissen wir, ob es Scopes, Timeout oder etwas Drittes ist. Zeiterfassung läuft.
florian.wilde worked for 18 minutes 2026-09-01 16:09:09 +00:00
florian.wilde added zu-testen and removed in-arbeit labels 2026-09-01 16:09:57 +00:00
Author
Owner

✅ Umsetzung abgeschlossen — bereit zum Testen. Schließt #19 und #20.

Was geändert wurde

  • Teilabrufe in lib/git/http.ts sammelt je Feld, was schiefging — mit HTTP-Status, denn daran hängt die Ursache: 403 Scopes, 429 Rate-Limit, 408 Zeitüberschreitung, 0 nicht erreicht. Der Fallback bleibt, ein einzelner Fehlschlag bricht den Abgleich nicht ab
  • Alle drei Clients (Gitea, GitHub, GitLab) benutzen ihn für ihre acht Teillisten
  • persistSnapshot lässt stehen, was nicht geholt werden konnte (#20). Abgeleitete Felder gehen mit ihrer Liste: lastCommit* an commits, latestTag an tags und releases
  • Sichtbar in der Meldung, als Hinweis am Repository auf der Projektseite (jedes Feld mit Grund, dazu das Datum des letzten vollständigen Standes) und als missing/completeAt am Snapshot
  • Ausdrücklich kein Fehlschlag: syncError bleibt leer, der Zähler fürs Zurückhalten steigt nicht
  • GIT_API_TIMEOUT_MS wird geprüft — Number("") ist 0, und setTimeout bricht damit jeden Aufruf sofort ab

Geänderte Dateien

  • src/lib/git/http.ts · src/lib/git/types.ts · gitea.ts · github.ts · gitlab.ts
  • src/lib/sync/index.ts · src/components/GitPanel.tsx · src/types/index.ts
  • prisma/schema.prisma · prisma/migrations/20260901160000_snapshot_teilabrufe/ (neu)
  • src/lib/git/http.test.ts (neu, 8 Tests) · FEATURES.md · README.md · CLAUDE.md

So testest du es

  1. git switch fix/19-abrufe-nicht-verschlucken
  2. npm run docker:db, dann npx prisma migrate deploy — zwei Spalten kommen mit
  3. npm run dev, ein Projekt mit Repository öffnen, Jetzt synchronisieren
  4. Erwartet bei gesundem Abgleich: unverändert „Snapshot aktualisiert: n Branches, m Tags."
  5. Der interessante Fall: trag in der Git-Verbindung ein kaputtes Token ein und synchronisiere erneut → die vorhandenen Branches, Tags und Commits müssen stehen bleiben, und darüber erscheint ein bernsteinfarbener Hinweis mit dem HTTP-Status je Feld
  6. Token zurücksetzen, erneut synchronisieren → Hinweis verschwindet von selbst

Geprüft: tsc sauber · 289 Unit-Tests grün (vorher 281) · npm run build durch · gegen einen nachgebauten Gitea-Server (Basis antwortet, Teillisten 403) und eine Wegwerf-Datenbank: nichts verloren, completeAt unverändert, nach dem nächsten gesunden Durchlauf wieder geräumt · axe-core 0 Verstöße auf 1280 px und 375 px.

Der eigentliche Grund der Ausfälle auf deiner Instanz ist damit noch nicht gefunden — aber nach dem nächsten Abgleich steht er auf der Projektseite.

Aufgewendete Zeit: 00:19
Pull Request: #21

Nach Freigabe: /issue-freigabe 19

✅ **Umsetzung abgeschlossen — bereit zum Testen.** Schließt #19 und #20. <!-- claude-status branch: fix/19-abrufe-nicht-verschlucken pr: 21 zeit_sekunden: 1113 --> **Was geändert wurde** - `Teilabrufe` in `lib/git/http.ts` sammelt je Feld, was schiefging — mit HTTP-Status, denn daran hängt die Ursache: 403 Scopes, 429 Rate-Limit, 408 Zeitüberschreitung, 0 nicht erreicht. Der Fallback bleibt, ein einzelner Fehlschlag bricht den Abgleich nicht ab - Alle drei Clients (Gitea, GitHub, GitLab) benutzen ihn für ihre acht Teillisten - **`persistSnapshot` lässt stehen, was nicht geholt werden konnte** (#20). Abgeleitete Felder gehen mit ihrer Liste: `lastCommit*` an `commits`, `latestTag` an `tags` **und** `releases` - Sichtbar in der Meldung, als Hinweis am Repository auf der Projektseite (jedes Feld mit Grund, dazu das Datum des letzten vollständigen Standes) und als `missing`/`completeAt` am Snapshot - Ausdrücklich **kein** Fehlschlag: `syncError` bleibt leer, der Zähler fürs Zurückhalten steigt nicht - `GIT_API_TIMEOUT_MS` wird geprüft — `Number("")` ist `0`, und `setTimeout` bricht damit jeden Aufruf sofort ab **Geänderte Dateien** - `src/lib/git/http.ts` · `src/lib/git/types.ts` · `gitea.ts` · `github.ts` · `gitlab.ts` - `src/lib/sync/index.ts` · `src/components/GitPanel.tsx` · `src/types/index.ts` - `prisma/schema.prisma` · `prisma/migrations/20260901160000_snapshot_teilabrufe/` *(neu)* - `src/lib/git/http.test.ts` *(neu, 8 Tests)* · `FEATURES.md` · `README.md` · `CLAUDE.md` **So testest du es** 1. `git switch fix/19-abrufe-nicht-verschlucken` 2. `npm run docker:db`, dann **`npx prisma migrate deploy`** — zwei Spalten kommen mit 3. `npm run dev`, ein Projekt mit Repository öffnen, **Jetzt synchronisieren** 4. Erwartet bei gesundem Abgleich: unverändert „Snapshot aktualisiert: n Branches, m Tags." 5. **Der interessante Fall:** trag in der Git-Verbindung ein kaputtes Token ein und synchronisiere erneut → die vorhandenen Branches, Tags und Commits müssen **stehen bleiben**, und darüber erscheint ein bernsteinfarbener Hinweis mit dem HTTP-Status je Feld 6. Token zurücksetzen, erneut synchronisieren → Hinweis verschwindet von selbst **Geprüft:** `tsc` sauber · 289 Unit-Tests grün (vorher 281) · `npm run build` durch · gegen einen nachgebauten Gitea-Server (Basis antwortet, Teillisten 403) und eine Wegwerf-Datenbank: nichts verloren, `completeAt` unverändert, nach dem nächsten gesunden Durchlauf wieder geräumt · axe-core 0 Verstöße auf 1280 px und 375 px. **Der eigentliche Grund der Ausfälle auf deiner Instanz ist damit noch nicht gefunden** — aber nach dem nächsten Abgleich steht er auf der Projektseite. **Aufgewendete Zeit:** 00:19 **Pull Request:** #21 Nach Freigabe: `/issue-freigabe 19`
Author
Owner

🚀 Freigegeben und nach main übernommen.

  • Merge-Commit: 97ba863
  • Pull Request: #21 (gemergt, schließt auch #20)
  • Gesamtaufwand: 00:19

⚠️ Beim Ausrollen läuft eine Migration mit (20260901160000_snapshot_teilabrufe, zwei Spalten an RepoSnapshot). scripts/update.sh ruft prisma migrate deploy selbst auf.

Nach dem nächsten Abgleich steht der HTTP-Status der fehlgeschlagenen Teil-Abrufe auf der Projektseite — damit lässt sich die eigentliche Ursache benennen.

Issue wird geschlossen.

🚀 **Freigegeben und nach `main` übernommen.** <!-- claude-status merge: 97ba8633564f6571fd66b64e154cf083b38159fd pr: 21 zeit_sekunden: 1113 --> - Merge-Commit: `97ba863` - Pull Request: #21 (gemergt, schließt auch #20) - Gesamtaufwand: 00:19 ⚠️ **Beim Ausrollen läuft eine Migration mit** (`20260901160000_snapshot_teilabrufe`, zwei Spalten an `RepoSnapshot`). `scripts/update.sh` ruft `prisma migrate deploy` selbst auf. Nach dem nächsten Abgleich steht der HTTP-Status der fehlgeschlagenen Teil-Abrufe auf der Projektseite — damit lässt sich die eigentliche Ursache benennen. Issue wird geschlossen.
florian.wilde removed the zu-testen label 2026-09-09 20:05:54 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Total Time Spent: 18 minutes
florian.wilde
18 minutes
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: florian.wilde/nebula#19