Skip to content

fix(jobs): name model provider failures instead of "KI-Dienst nicht erreichbar" - #82

Merged
Fluory merged 3 commits into
mainfrom
claude/fix-model-overload-cause-80
Sep 27, 2026
Merged

Fluory merged 3 commits into
mainfrom
claude/fix-model-overload-cause-80

Conversation

@Fluory

@Fluory Fluory commented Sep 27, 2026 •

Copy link
Copy Markdown
Owner

Warum

Fixes #80 · Befund aus dem Live-Test des Showcase am 2026-09-27 (Epic #19)

Arbeitsstand

  • Ziel: Sachbearbeitung liest die tatsächliche Ursache eines KI-Fehlers – ein gestörtes Modell beim Anbieter ist nicht „unser Dienst ist nicht erreichbar“.
  • Nicht-Ziele: Wiederholungslogik, KI-Dienst selbst, neue Fehlercodes im Vertrag.
  • Erledigt: Client behält den Vertrags-Fehlercode wiederholbarer Antworten; eigene Texte je Ursache; Unit- und Integrationstests; CHANGELOG; Hinweise des frischen Reviews umgesetzt (Fehlertext höchstens 4 KiB, hängender Fehlertext = Zeitüberschreitung).
  • Offen: Merge (Freigabe des Orchestrators).
  • Annahmen: keine.
  • Nächster kleinster Schritt: Merge nach Freigabe, danach Sichtprüfung im Showcase beim nächsten Überlast-Fall.

Was ist passiert (Klartext)

Beim Live-Test war Googles Modell zweimal überlastet. Unser KI-Dienst hat das korrekt gemeldet, die Web-App zeigte aber „Der KI-Dienst ist nicht erreichbar.“ – so, als wäre unser eigener Dienst ausgefallen. Genau dieser Satz hat auch den externen Reviewer in die Irre geführt. Jetzt steht dort, was wirklich los ist: „Das KI-Modell des Anbieters war nicht verfügbar (z. B. überlastet).“ Ist unser Dienst ausgelastet, gestört oder wirklich nicht erreichbar, gibt es dafür jeweils einen eigenen, klaren Satz. Kein Text verspricht einen neuen Versuch, denn der Satz bleibt auch stehen, wenn alle Versuche aufgebraucht sind. Den nächsten Versuch zeigt die Seite ohnehin separat an.

Plan-Pflicht (SYSTEM.md §4)

  • Kein Auslöser – keine Modulgrenze, öffentliche API, Migration, Auth/Rechte, kein Zahlungs-/Daten-/Infrapfad, höchstens zwei Module, keine Architekturvarianten, umkehrbar
  • Auslöser zutreffend – Impact Manifest ausgefüllt (Plan vor Code)

Geändert

  • src/features/extraction/ai-client.ts: bei wiederholbaren HTTP-Status liest der Client höchstens 4 KiB der Antwort (Rest wird verworfen, nicht gepuffert; ein Hängen bis zum Timeout zählt als timeout) und nur das Fehlerobjekt und behält dessen Vertrags-Code (model_error, model_output_invalid, busy, internal_error …); ohne gültigen Code (z. B. eine 502-Seite der Plattform) bleibt es bei unavailable. Die Code-Liste ist ein Record über den generierten Vertragstyp – ein neuer Code im Vertrag bricht den Build, bis er eingetragen ist.
  • src/features/jobs/process-request.ts: describeFailure() mit eigenem Text je Ursache
  • src/features/jobs/describe-failure.test.ts (neu), src/features/extraction/ai-client.test.ts: Fälle je Code, HTML-Fehlerseite, keine Versprechen, keine Hosts/Status
  • tests/integration/processing.test.ts: nackter 503 → „vorübergehend gestört“ (vorher „nicht erreichbar“); neuer Fall 502 model_error → Anbieter-Text bis in die gespeicherte Anfrage
  • CHANGELOG.md: Fixed-Eintrag fix(jobs): name model overload instead of "KI-Dienst nicht erreichbar" #80

Nachweis (SYSTEM.md §11)

  • verify:changed: grün – vitest ai-client.test.ts describe-failure.test.ts 40/40; zuerst rot: 9 neue Fälle (Code unavailable statt Vertrags-Code, alter Text), nach dem Review 2 weitere (übergroßer und hängender Fehlertext)
  • verify: lokal – lint 0 Fehler, typecheck und depcruise grün; Unit 221/227, die 6 Fehlschläge sind ausschließlich der Windows-Fall tests/architecture/dependency-rules.test.ts (fix(tests): architecture test cannot start dependency-cruiser on Windows #78), in der Linux-CI grün; Integration processing.test.ts 14/14 gegen Postgres + S3; volle CI am PR
  • verify:full / E2E-Spec: nicht betroffen (die Smoke-Specs prüfen keine Fehlertexte)
  • Manueller Prüfnachweis: Ausgangsbefund live belegt (Logs requestflow-ai 2026-09-27 10:57 und 10:58: model_call_retry 503 ×2, dann 502 model_error; die Seite zeigte „nicht erreichbar“). Sichtprüfung des neuen Textes nach dem Deploy, sobald Gemini wieder überlastet antwortet – nicht gezielt auslösbar.
  • Frischer Review (P1 vor Ready-for-review; Architektur/API/DB immer): fix(jobs): name model provider failures instead of "KI-Dienst nicht erreichbar" #82 (comment) – mergeable per risk matrix; Hinweise 1 und 2 umgesetzt, Hinweis 3 unter Risiken

Doku-Entscheidung (genau eine)

Entferntes oder Umbenanntes: docs/ + README gegrept, Treffer bereinigt: docs/technical/operations.md:25 nennt „nicht erreichbar“ für den lokalen Betrieb ohne KI-Dienst – das ist ein Netzwerkfehler und bleibt korrekt

Dateigrößen und neue Bausteine (SYSTEM.md §7)

Dateien über 500 Zeilen im Diff (Ausnahmen: generierter Code, Lockfiles, Fixtures, Migrationen, Schemas, Ressourcen, Doku, Konfiguration):

  • keine
  • bewusst belassen – Begründung: –
  • im selben PR nach fachlicher Verantwortung geteilt
  • Folge-Issue –

Über 800 Zeilen mit neuer Fachlogik oder über 1000 Zeilen (P1/P2): nicht betroffen

Neue Shared-Komponente, Utility-Datei, Adapter oder fachlicher Service:

  • nein
  • ja – gesucht nach: –

Subagent-Einsätze

Keine.

Risiken / offene Punkte

  • Der Client liest bei wiederholbaren Fehlern jetzt höchstens 4 KiB des Antwort-Bodys (bisher verworfen); alles, was nicht dem Vertrag entspricht, fällt auf unavailable zurück.
  • Der Log-Code von job.failed wird genauer: statt ai.unavailable jetzt ai.model_error, ai.busy, ai.internal_error oder ai.model_output_invalid. Im Repo wertet ihn niemand aus; externe Log-Suchen auf ai.unavailable müssten angepasst werden (es gibt derzeit keine).

🤖 Generated with Claude Code

Fluory and others added 2 commits September 27, 2026 14:46
…rreichbar"

In the live showcase test on 2026-09-27 Gemini answered 503 three times
per call; our AI service passed that on as 502 model_error, but the web
client dropped the error body and describeFailure() turned every
retryable failure into "Der KI-Dienst ist nicht erreichbar." - the text
that misled the external review. The client now keeps the contract's
error code for retryable answers (a Record over the generated codes, so
a new code fails the build), and staff read the actual cause. No text
promises a retry, because it stays after the attempts are used up.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
requestflow Ready Ready Preview Sep 27, 2026 1:26pm UTC
requestflow-ai Ready Ready Preview Sep 27, 2026 1:26pm UTC

@Fluory

Fluory commented Sep 27, 2026

Copy link
Copy Markdown
Owner Author

Frischer Review (/review-pr) – unabhängiger Reviewer-Agent ohne Umsetzungskontext, 2026-09-27

Review PR #82 (Fixes #80): mergebar laut Risikomatrix. Keine Blocker, nichts Wichtiges, drei Hinweise.

Geprüft

  • Die Texte stimmen byte-genau mit fix(jobs): name model overload instead of "KI-Dienst nicht erreichbar" #80 überein, auch „z. B.“.
  • Kein Text verspricht einen neuen Versuch. Keiner nennt Host oder Status.
  • Der Timeout-Text ist unverändert. Der Text für dauerhafte Fehler ist unverändert (process-request.ts:43).
  • Diese Paare sendet der KI-Dienst, alle haben einen passenden Text: 429 busy (app.py:307); 500 internal_error (app.py:196, :226, :333); 502 model_output_invalid (app.py:395); 502 model_error (app.py:396).
  • Was nicht dem Vertrag entspricht, wird zu unavailable und damit „vorübergehend gestört“ (Whitelist in ai-client.ts:67-86). Das gilt für Plattformseiten, leeren Body, fremden JSON-Code und kaputtes JSON.
  • Die Whitelist hält beliebige Body-Strings aus Logs und pg-boss-Output fern.
  • processing.test.ts:192: berechtigte Spec-Änderung, keine Abschwächung (nackter 503 ohne Body → laut AK „vorübergehend gestört“; ERROR, attempts 2, nextRetryAt null und „kein Host“ bleiben geprüft; neuer Grenztest :196-207).
  • Unit-Tests lokal 38/38; CI check (inkl. Integration) und compose-smoke grün.
  • Modulgrenzen eingehalten; Plan-Pflicht-Antwort ehrlich; CHANGELOG korrekt; docs/technical/operations.md:25 bleibt richtig; Klartext verständlich.

Befunde

[Note] – src/features/extraction/ai-client.ts:83-85 – Bei wiederholbaren Status liest `response.json()` den Body ohne Größengrenze; begrenzt ist nur die Zeit (AbortSignal, Default 120 s, env.ts:36). Verifiziert mit Node-24-Skript: Ein 50-MB-Body wird vollständig gepuffert und sein Code übernommen. – Geringes Risiko, weil die Gegenstelle der eigene Dienst bzw. die Plattform ist. AK1 „without reading more than the error object“ ist nur inhaltlich erfüllt (zod behält nur `error.code`), nicht in der Größe. – Bei `content-length` > ~4 KiB Body mit `cancel()` verwerfen → `unavailable`, oder die Lesart des AK im PR festhalten.

[Note] – src/features/extraction/ai-client.ts:84 – Hängt der Body nach den Headern, fängt `.catch` den TimeoutError ab. Ergebnis erst nach dem vollen Timeout: „vorübergehend gestört“ und `ai.unavailable` statt „nicht rechtzeitig geantwortet“ und `ai.timeout` (verifiziert per Skript, 800-ms-Timeout → nach 836 ms abgefangen). – Seltener Randfall, der Text bleibt ehrlich. – Optional einen TimeoutError aus `json()` als `timeout` einordnen, sonst so lassen.

[Note] – src/features/jobs/drain.ts:73 (nicht im Diff) – Der Log-Code von `job.failed` wechselt von `ai.unavailable` zu `ai.model_error`, `ai.busy`, `ai.internal_error` bzw. `ai.model_output_invalid`. Im Repo liest ihn niemand (grep in src, tests und docs ohne Treffer). – Log-Suchen oder Alerts außerhalb des Repos, die auf `ai.unavailable` filtern, übersehen künftig Anbieterfehler. – Unter „Risiken“ im PR einen Satz ergänzen; kein Code nötig.

Urteil: „mergeable per risk matrix“. Alle Akzeptanzkriterien sind mit Tests belegt, die fehlschlagen können, auch an der echten Grenze. CI ist grün. Kein Auslöser für eine menschliche Freigabe. Mergen soll der andere Account (Regel 5).

…d one as timeout

The fresh review of #82 found that the error body of a retryable answer
was read without a size limit (a 50 MB body was buffered to get one
code) and that a body stalling after the headers ended as "unavailable"
instead of "timeout". The client now reads at most 4 KiB of an error
body and cancels the rest; a timeout while reading stays a timeout.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Fluory

Fluory commented Sep 27, 2026

Copy link
Copy Markdown
Owner Author

Merge auf ausdrückliche Freigabe des Orchestrators (2026-09-27: „Ok“).

@Fluory
Fluory merged commit adfec27 into main Sep 27, 2026
7 of 9 checks passed

This branch was successfully deployed

2 active deployments
Preview – requestflow-ai — 68bc40be Deployed Sep 27, 2026 by vercel[bot]
Preview – requestflow — 68bc40be Deployed Sep 27, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(jobs): name model overload instead of "KI-Dienst nicht erreichbar"

1 participant