CluPilotCloud/docs/handoffs/2026-07-28-session-handoff.md

11 KiB

Handoff — CluPilot (Stand 2026-07-28, Abend)

Für: frische Claude-Code-Session Zuerst lesen: Abschnitt 0 dieses Dokuments, dann CLAUDE.md (R22 und R23 zuerst), dann git log --oneline -15. Vorgänger: docs/handoffs/2026-07-28-operator-identity-handoff.md (Umgebung, Stack, Befehle — gilt weiter).


0. Vor allem anderen: wie in dieser Sitzung gearbeitet wurde, und warum das falsch war

Der Nutzer hat mehrfach und zuletzt sehr deutlich gesagt: jeder Prompt dauert mindestens eine Stunde, der Tokenverbrauch ist enorm — beim letzten Schritt rund eine Million. Das ist keine Stilfrage, das kostet ihn sein Wochenkontingent.

Die Ursachen lagen im Verfahren, nicht in den Aufgaben:

  • Für jede Kleinigkeit wurde ein eigener Subagent mit Review, Fix-Runde und Re-Review gefahren.
  • Der Codex-Review (R15) wurde bis zur Nullmeldung geschliffen — zehn Runden für eine Tabellenumstellung.
  • Es wurden wiederholt Dateien geprüft, die seit dem letzten grünen Testlauf niemand angefasst hatte.
  • Mutationstests flächendeckend statt nur an der Sicherheitsgrenze.

R22 in CLAUDE.md ist die verbindliche Konsequenz. Halte dich daran, auch wenn ein Befund verlockend aussieht:

  1. Eine Prüfrunde, eine Fix-Runde, ein Re-Review über den Fix-Diff — danach parken, nicht weiterschleifen.
  2. Codex/R15: höchstens zwei Runden ohne P1. Rest als Folgepunkt in den Merge Request.
  3. Nie Dateien prüfen, die seit dem letzten grünen Lauf unverändert sind.
  4. Mutationstests nur, wo ein Fehler Daten verliert, Rechte ausweitet oder Zugangsdaten preisgibt.
  5. Aufgaben ohne Entwurfsentscheidung bekommen keinen eigenen Review.
  6. Unabhängige Analysen parallel starten.

Zusätzlich, aus dieser Sitzung gelernt:

  • Kleine UI-Änderungen selbst machen. Ein Sprachschlüssel, eine Beschriftung, eine Tailwind-Klasse: bauen, Suite laufen lassen, fertig. Ein Subagent dafür kostet mehr, als er bringt.
  • Erst messen, wo der Fehler liegt, dann handeln. In dieser Sitzung wurden zweimal Schlüsse aus einem fehlerhaften eigenen Messaufbau gezogen und dem Nutzer als Befund über sein System verkauft (einmal ein leeres Passwort im Prüfskript, einmal fünfzehn Zeilen Log als „dein Browser erreicht den Container nicht"). Beides war falsch und hat ihn auf falsche Fährten geschickt.
  • Bei Umgebungsfragen zuerst klären, welche Umgebung gemeint ist. dev und live haben getrennte Datenbanken, getrennte Netze und getrennte .env. Mehrere Runden gingen dafür drauf, das zu verwechseln.

1. Stand

main steht auf 64efe45, gepusht, 924 Tests grün, Arbeitsbaum sauber. Live folgt main über Konsole → Einstellungen → Jetzt aktualisieren.

Seit dem letzten Handoff gelandet:

Commit Inhalt
1641454 Abgelaufene Betreiber-Sitzung führt zur Anmeldung statt zu einer 500
c2681f2 Vollbild-Wartungsansicht auf jeder Konsolenseite während eines Updates
973fcb3 R23: native confirm()-Dialoge durch die eigenen Modale ersetzt, sechs Stellen, Scan-Test
f055479 Update: Prüfen von Aktualisieren getrennt, Fortschritt und Countdown
bda61ca Feste Kopf-/Fußzeile in beiden Shells, zwei umbrechende Beschriftungen gekürzt
64efe45 419 lädt still neu statt Overlay; Offline-Banner mit „Verbindung wiederhergestellt"

2. Offene Punkte, mit dem was bereits gemessen wurde

2.1 Update-Prüfung startet einen Timer, den niemand wollte — falsch umgesetzt

Der Nutzer, wörtlich: „ich suche nur nach neuen Updates, was ist das da für ein Timer, den ich nicht gestartet habe? Ich wollte nur prüfen. Beim Prüfen soll in dem Moment die Abfrage kommen, nicht in 5 Minuten — das ist ja Unsinn, wenn das so gemacht worden ist."

Sein Screenshot zeigt nach Klick auf Nach Aktualisierungen suchen: Badge „Prüfung läuft", Knopf Jetzt aktualisieren aktiv, und „Startet in 3:44".

Was schiefging: Die Prüfung wurde als Anforderungsdatei umgesetzt, die derselbe Host-Agent auf derselben Zeitschleife abholt wie ein echtes Update (deploy/update-agent.sh, CHECK_INTERVAL_MINUTES). Eine reine Abfrage darf nicht in einer Warteschlange landen — sie soll sofort antworten. Zusätzlich liest der Countdown wie „gleich startet ein Update", obwohl nur geprüft wird.

Zu entwerfen, nicht bloß zu flicken. Denkbare Richtungen — bewusst offen gelassen, weil die richtige Antwort von update-agent.sh abhängt:

  • Die Prüfung ohne Agent machen (ein git ls-remote/fetch aus dem Container, falls das Repo von dort erreichbar ist) und sofort antworten.
  • Oder den Agenten für Prüfanforderungen unabhängig vom Timer aufwecken.
  • In jedem Fall: kein Countdown bei einer reinen Prüfung.

Anmerkung: CHECK_INTERVAL_MINUTES steht im Repo bereits auf 1, greift auf einem Server aber erst nach erneutem deploy/install-agent.sh — der Wert lebt im systemd-Timer. Auf live standen deshalb noch 5 Minuten.

2.2 Rechenzentren: Kürzel nicht änderbar, Bezeichnung fehlt

Zwei Wünsche:

  1. Das Kürzel (fsn, hel) lässt sich beim Anlegen setzen, aber nicht mehr bearbeiten. Soll änderbar sein. Vorsicht: Es ist vermutlich Fremdschlüssel oder Anzeigeschlüssel an anderen Stellen — vor dem Bauen prüfen, was daran hängt (Hosts, Bestellungen), und ob eine Änderung nachziehen muss.
  2. Ein optionales Feld für das konkrete Rechenzentrum, z. B. fsn-dc-15, damit bei der Auswahl erkennbar ist, welches gemeint ist.

Betroffen: App\Livewire\Admin\EditDatacenter + resources/views/livewire/admin/edit-datacenter.blade.php, Migration für das neue Feld, Datacenter-Modell.

2.3 500 beim VPN-Konfigurations-Download auf live — weiterhin ungeklärt

Auf dev funktioniert der Download. Auf live: Passwort eingeben → 500.

Was bereits ausgeschlossen ist:

  • Nicht der Null-Operator-Fehler — der Fix (1641454) ist auf live, die Fußzeile im Screenshot zeigte c2681f2.
  • ConfigVault::decrypt() wirft nicht, es gibt null zurück → das ergäbe eine saubere Meldung, keine 500.
  • Auf dev hat der Peer kein gespeichertes Konfigurat (config_secret leer, weil er vor dem Setzen von VPN_CONFIG_KEY angelegt wurde) — dort kommt deshalb die Meldung „nicht lesbar", nicht der Fehlerfall von live.

Was fehlt: eine aktuelle Protokollzeile vom Live-Server zum Zeitpunkt der 500. Das vom Nutzer geschickte Protokoll ist vom 27.07. 04:16 und betrifft artisan optimize, nicht den Download — siehe 2.4. Frage gezielt danach:

docker compose exec -T app tail -100 storage/logs/laravel.log | grep -B2 -A20 "local.ERROR"

Und zusätzlich: ist VPN_CONFIG_KEY auf live gesetzt, und ist es derselbe wie beim Anlegen des Peers? Ein gewechselter Schlüssel macht bestehende Konfigurationen unlesbar.

2.4 route:cache scheitert auf live — eigener, echter Fund

Aus dem Protokoll des Nutzers, 2026-07-27 04:16:

production.ERROR: Unable to prepare route [/] for serialization.
Another route has already been assigned name [admin.overview].

Das passiert in php artisan optimizeroute:cache. Schlägt das fehl, bleibt die Anwendung ohne gültigen Route-Cache — ein plausibler Grund für 500er, die nur auf live auftreten, weil dev kein optimize fährt.

Auf dev nicht reproduzierbar: route:cache läuft mit ADMIN_HOST_EXCLUSIVE=true und einem einzigen Hostnamen sauber durch (geprüft).

Verdacht, nicht bewiesen: live hat mehrere Einträge in ADMIN_HOSTS, und die Registrierung in routes/web.php vergibt dabei zweimal denselben Namen. Der Block registriert seit dieser Sitzung zwei Gruppen je Host (Gast + authentifiziert). Erste Diagnose auf live:

docker compose exec -T app php artisan tinker --execute="echo json_encode(config('admin_access.hosts')).PHP_EOL;"
docker compose exec -T app php artisan route:cache

2.5 Live-Konfiguration, die noch aussteht

  • SECRETS_KEY — ohne ihn speichert die Konsole keine Zugangsdaten (Postfächer, Stripe, DNS, Monitoring). Erzeugen mit head -c 32 /dev/urandom | base64, in die .env, danach config:clear und docker compose restart queue queue-provisioning scheduler reverb (die langlaufenden Prozesse lesen die .env nur beim Start).
  • MAIL_SCHEME=tls ist ungültig — Symfony kennt nur smtp und smtps. .env.example ist korrigiert, die .env der Server nicht.
  • deploy/install-agent.sh einmal neu laufen lassen, damit das 1-Minuten-Intervall greift.

2.6 Aus dem vorigen Handoff weiterhin offen

22 Konsolen-Blades aufs Designsystem · Support-Warteschlange in der Konsole (jetzt baubar, Absender existieren) · Startseite ins Blade · E-Mail-Vorlagen · verschoben gespeicherte Wartungsfenster · Mitarbeiterverwaltung als Ansicht · Passwort-Zurücksetzen für Operatoren.


3. Fallen, die diese Sitzung Zeit gekostet haben

  • actingAs() ruft Auth::shouldUse() und macht den genannten Guard zum Standard — damit verschwindet jeder Unterschied zwischen auth()->user() und auth()->guard('x')->user(). Hat zwei echte Fehler verdeckt. Für Guard-Nachweise mit Auth::guard('x')->login() anmelden.
  • Pest: toThrow(SomeInterface::class) degradiert still zu einem String-Vergleich, weil class_exists() für Interfaces false ist. Nie gegen Interface-Namen prüfen.
  • SQLite verdeckt Migrationsfehler, die MariaDB zeigt (Transaktionen um DDL, doppelt gelöste Constraints, zusammenfallende Auto-Increment-Werte). Migrationen gegen die echte dev-Datenbank prüfen.
  • <x-ui.button> hat keinen Icon-Slot — ein <x-slot:icon> wird still verschluckt. Icon in den Standard-Slot mit class="size-4".
  • Es gibt kein layouts.guest — Auth-Seiten benutzen layouts.portal.
  • users hat keine uuid-Spalte; Customer und Operator schon. Ein Kunde hängt über customers.user_id am Benutzer, nicht über users.customer_id.
  • Mailserver sperren IPs nach wiederholten Fehlanmeldungen. Ein Testversand mit falschem Passwort zählt darauf ein; danach kommen Timeouts, keine Ablehnungen. In dieser Sitzung war genau das die Ursache für „live geht gar nichts mehr" — vom Nutzer per Whitelist gelöst.

4. Was der Nutzer erwartet

Er baut ein Produkt und will Ergebnisse sehen, keine Verfahren. Konkret:

  • Zuerst liefern, dann erklären. Kein Vorlauf aus Analyse, wenn die Aufgabe klein ist.
  • Kein Verfahren um seiner selbst willen. Wenn eine Änderung zwei Zeilen groß ist, ist sie zwei Zeilen groß.
  • Ehrlich sein, wenn etwas nicht geht. Er nimmt ein „das kann ich von hier nicht prüfen, gib mir bitte diese eine Ausgabe" besser auf als eine Vermutung, die sich als falsch herausstellt.
  • Umgebung immer benennen. dev oder live — sonst reden beide Seiten aneinander vorbei.