diff --git a/CLAUDE.md b/CLAUDE.md
index 8ff6419c..6082b40d 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -162,6 +162,16 @@ Also relevant:
`percona-toolkit` on the server, which is deliberately **not** part of the
standard setup.
+### Fehlerprotokoll (GlitchTip) and operations
+
+Errors from production are collected in a self-hosted GlitchTip
+(`errors.adametz.media`, project 1 = server, 2 = browser). When asked to
+look at GlitchTip or at production errors, follow
+`dev/glitch-tip/README.md` — it contains the read-only API commands
+(token `GLITCHTIP_API_TOKEN` in `.env`, never print it), the triage
+workflow and the privacy rules for error reporting. Monitoring decisions:
+`dev/betrieb/monitoring-plan.md`; deploys: `dev/betrieb/deploy-runbook.md`.
+
### Testing Framework
- **Pest PHP** (v3.8+) - Modern testing framework built on PHPUnit
diff --git a/dev/betrieb/monitoring-plan.md b/dev/betrieb/monitoring-plan.md
index 397c2caf..153e9d13 100644
--- a/dev/betrieb/monitoring-plan.md
+++ b/dev/betrieb/monitoring-plan.md
@@ -195,3 +195,11 @@ Mit Deploy v1.1.0 ausgeliefert ([`deploy-v1.1.0.md`](deploy-v1.1.0.md)).
GlitchTip (`production`), ntfy und Scheduler-Herzschlag auf Live aktiv.
Offen aus Phase 1: externer Monitor auf `/health`; DSE-Absätze, danach
`UMAMI_PORTAL_ENABLED`/`UMAMI_IDENTIFY_USERS`.
+
+### 06.10.2026 — Erste Auswertung von GlitchTip
+
+Lesezugriff per API eingerichtet; Arbeitsanleitung, Regeln und Protokoll der
+Einträge in [`../glitch-tip/README.md`](../glitch-tip/README.md). Befunde:
+Testsuite meldete an die echte DSN (behoben, `2f02acc`), Search-Console-Lauf
+robuster (`a90f24a`), Registrierung mit Alpine-Fehler, Promise-Ablehnungen
+lesbar, Tippfehler bei artisan nicht mehr gemeldet (`16af374`).
diff --git a/dev/glitch-tip/README.md b/dev/glitch-tip/README.md
new file mode 100644
index 00000000..2db83d01
--- /dev/null
+++ b/dev/glitch-tip/README.md
@@ -0,0 +1,190 @@
+# GlitchTip — Fehlerprotokoll von Pressekonto
+
+**Stand:** 06.10.2026 · **Gilt ab:** v1.1.0 (live seit 05.10.2026)
+**Für:** KI-Chats und Entwickler, die Fehler aus GlitchTip auswerten und beheben.
+
+Dieses Dokument beschreibt, **was** GlitchTip hier ist, **wie** Fehler dorthin
+gelangen, **was zu tun ist**, wenn jemand sagt „schau mal in GlitchTip“, und
+welche Regeln dabei nicht verletzt werden dürfen. Hintergrund und
+Entscheidungen: [`../betrieb/monitoring-plan.md`](../betrieb/monitoring-plan.md)
+(Phase 1, Entscheidungen M1–M9).
+
+---
+
+## 1. Überblick
+
+| Was | Wert |
+|---|---|
+| Instanz | `https://errors.adametz.media` (selbst gehostet, Sentry-kompatibel) |
+| Organisation (Slug) | `adametz-media` |
+| Projekt 1 | `server-pressekonto` — PHP/Laravel: Exceptions und Fehlersignale |
+| Projekt 2 | `browser-pressekonto` — JavaScript-Fehler aus dem Browser |
+| Umgebungen | `production` (Live), `local` (Entwicklung). Nur `production` ist Betrieb |
+| Release | Git-Tag aus der Datei `RELEASE` (z. B. `v1.1.0`), siehe Abschnitt 6 |
+| Aufbewahrung | 90 Tage (`GLITCHTIP_MAX_EVENT_LIFE_DAYS` auf dem GlitchTip-Server) |
+| Alarmierung | GlitchTip-Alert-Regel: Mail bei neuem Fehlertyp. Betriebs-Alerts der App (fehlgeschlagene Jobs/Tasks) laufen getrennt über Mail + ntfy (`push.adametz.media`, Topic `pressekonto-ops`) |
+
+GlitchTip ist **nicht** das einzige Signal. Daneben: `/health` (externer
+Monitor), Admin ▸ Reports ▸ Betriebsüberwachung, `storage/logs/*.log`.
+
+---
+
+## 2. Wie Fehler nach GlitchTip gelangen
+
+### Server (Projekt 1)
+
+| Weg | Datei | Was |
+|---|---|---|
+| Gemeldete Exceptions | `bootstrap/app.php` → `Integration::handles($exceptions)` | Alles, was Laravel über `report()` meldet — unbehandelte Exceptions in Requests, Jobs, Commands |
+| Fehlersignale ohne Exception | `app/Http/Middleware/ReportErrorSignals.php`, `app/Support/Monitoring/ErrorSignal.php` | Warnungen: 404 mit eigenem Referrer („Toter interner Link“), 403/429 für Angemeldete, 419 („Seite abgelaufen“). Je Signal und Ort höchstens 5 Meldungen in 10 Minuten |
+| Konfiguration | `config/sentry.php` | DSN, Release, Datenschutz-Optionen |
+| Datenschutzfilter | `app/Support/Monitoring/ErrorReportScrubber.php` (`before_send`) | Nur User-ID, Tokens/Signaturen aus URL/Query/Referer, E-Mails aus Meldungen |
+
+**Bewusst nicht gemeldet:**
+- Laravels Standard-`dontReport`: 404, Validierung, Authentifizierung, 419 als Exception (419 kommt als Fehlersignal).
+- `CommandNotFoundException` — vertippte `artisan`-Befehle (`bootstrap/app.php`).
+- `App\Services\SearchConsole\SearchConsoleUnavailable` — Google hängt vorübergehend; nur `Log::warning`, der Lauf alarmiert erst bei Abbruch oder ≥ 50 % Fehlern.
+
+### Browser (Projekt 2)
+
+| Datei | Was |
+|---|---|
+| `resources/js/error-reporting.js` | Hört auf `error` und `unhandledrejection`; lädt das SDK erst beim ersten Fehler; filtert URLs/E-Mails; Ablehnungen ohne `Error` werden mit Typ und Inhalt beschrieben |
+| `resources/js/error-reporting-sdk.js` | Re-Export der benötigten SDK-Funktionen (Tree-Shaking) |
+| `resources/views/components/monitoring/browser-errors.blade.php` | `` mit DSN, Release, User-ID, Bereich; nur mit `SENTRY_BROWSER_DSN` |
+| Eingebunden in | `partials/head` (Portal, `area: portal`), `web/layouts/web-master` (`area: web`), `components/layouts/auth/pressekonto` (`area: auth`) |
+
+Fehlgeschlagene Livewire-Requests (5xx, 419) meldet der **Server**, nicht der Browser.
+
+### Tests melden nie an GlitchTip
+
+`phpunit.xml` leert `SENTRY_LARAVEL_DSN`, `SENTRY_DSN`, `SENTRY_BROWSER_DSN`.
+Wächtertest: `tests/Feature/Monitoring/ErrorReportingTest.php` („the test suite
+never reports to the real glitchtip“). Tests, die Sentry-Ereignisse prüfen,
+benutzen `Tests\Support\FakeGlitchTip::install()`.
+
+**Erkennungszeichen für Test-Rauschen** in GlitchTip: Umgebung `local` und
+Texte wie „Queue weg“, „Datenbank nicht erreichbar“, SQLite-Fehler
+(„General error: 1 no such table“ — Live ist MySQL), „no role named
+`customer`“. Bis 06.10.2026 kamen solche Einträge aus Testläufen mit lokaler DSN.
+
+---
+
+## 3. Zugriff per API (lesend)
+
+Token: `GLITCHTIP_API_TOKEN` in der lokalen `.env` — **nur lesen**
+(`project:read`, `event:read`). Kein `org:read` (Organisationsliste → 403),
+kein Schreiben (Auflösen → 403). Den Token nie ausgeben, nie committen.
+
+Alle Befehle aus dem Projektverzeichnis:
+
+```bash
+T=$(grep -E "^GLITCHTIP_API_TOKEN=" .env | cut -d= -f2- | tr -d "'\"")
+B=https://errors.adametz.media/api/0
+```
+
+**Offene Einträge eines Projekts** (Projekt 1 = Server, 2 = Browser; `environment=production` für Live, weglassen für alle):
+
+```bash
+curl -s -H "Authorization: Bearer $T" \
+ "$B/organizations/adametz-media/issues/?project=1&query=is:unresolved&environment=production&limit=100" \
+| python3 -c "
+import json,sys
+for i in json.load(sys.stdin):
+ print(i['id'], i['level'], i['count'], i['firstSeen'][:16], i['lastSeen'][:16], '|', (i['title'] or '')[:100])"
+```
+
+**Letztes Ereignis eines Eintrags** mit Umgebung, Release, URL und Stacktrace:
+
+```bash
+curl -s -H "Authorization: Bearer $T" "$B/issues//events/latest/" | python3 -c "
+import json,sys
+e=json.load(sys.stdin); tags={t['key']:t['value'] for t in e.get('tags',[])}
+print('env:',tags.get('environment'),'| release:',tags.get('release'),'| area:',tags.get('area'),'| browser:',tags.get('browser'))
+for en in e.get('entries',[]):
+ if en['type']=='exception':
+ for v in en['data'].get('values',[]):
+ print('EXC',v.get('type'),':',(v.get('value') or '')[:300])
+ for f in ((v.get('stacktrace') or {}).get('frames') or [])[-8:]:
+ print(' ',f.get('filename') or f.get('absPath'),f.get('function'),f.get('lineNo'),f.get('colNo'))
+ if en['type']=='request': print('REQ',en['data'].get('url'))"
+```
+
+**Mehrere Ereignisse eines Eintrags:** `$B/issues//events/?limit=10`.
+**Projekte:** `$B/projects/`.
+
+Weboberfläche: `https://errors.adametz.media/adametz-media/issues?project=1`.
+
+---
+
+## 4. Aufgabe: GlitchTip auswerten und Fehler beheben
+
+Wenn der Auftrag lautet „schau in GlitchTip“, „was sagt GlitchTip“ o. Ä.:
+
+1. **Abrufen.** Offene Einträge beider Projekte, zuerst `environment=production`, dann ohne Filter.
+2. **Einordnen** — jeder Eintrag in genau eine Klasse:
+
+ | Klasse | Erkennen | Vorgehen |
+ |---|---|---|
+ | Test-/Entwicklungsrauschen | Umgebung `local`, Texte aus Tests (Abschnitt 2) | Nichts am Code; Betreiber markiert „Resolved“. Taucht es mit `production` auf → echter Fehler |
+ | Bedienfehler am Server | z. B. `CommandNotFoundException` | In `dontReport` aufnehmen, falls wiederkehrend |
+ | Externer Dienst vorübergehend | Timeouts/5xx von Google, OpenAI, Stripe … | Retry + Warnung statt `report()`, Abbruch- und Zeitbudget; Muster: `SearchConsoleClient` + `SearchConsoleUnavailable` |
+ | Echter Bug | Stacktrace in `app/`, `resources/`, Browserfehler mit eigener Datei/URL | Schritte 3–6 |
+ | Unklar | z. B. `Error: [object Object]` | Erfassung verbessern (mehr Kontext, keine personenbezogenen Daten), auf das nächste Auftreten warten |
+
+3. **Code finden.** Stacktrace (`filename`, `function`, `lineNo`), `culprit`, URL (`REQ`), Bereich (`area`). Bei Browserfehlern zeigen die Frames oft nur `livewire.min.js` — der Hinweis steckt dann im Funktionsnamen (z. B. `[Alpine] __('…')` = Alpine-Ausdruck) → in `resources/views` nach dem Ausdruck suchen.
+4. **Reproduzieren mit rotem Test**, dann Fix (Projektregel: jede Änderung mit Test). Ist der Fehler ein Muster, das wieder auftreten kann, einen **Wächtertest** über alle Dateien schreiben (Beispiele: `BladePhpBlockGuardTest`, `BladeAlpineTranslationGuardTest`).
+5. **Qualitätsgates:** betroffene Tests, dann `php artisan test --compact --parallel`, `vendor/bin/phpstan analyse --memory-limit=2G`, `vendor/bin/pint --dirty --format agent`; bei JS `npm run build`.
+6. **Dokumentieren:** Eintrag in Abschnitt 7 (ID, Klasse, Ursache, Fix, Commit). Commit-Nachricht nennt die GlitchTip-ID.
+7. **Ausliefern** über den nächsten Deploy (Hotfix: letzte Versionsstelle, z. B. `v1.1.1`; Ablauf wie [`../betrieb/deploy-v1.1.0.md`](../betrieb/deploy-v1.1.0.md)).
+8. **Auflösen** in GlitchTip macht der Betreiber (Token ist nur lesend) — erst nach dem Deploy. Kommt der Fehler mit neuem Release wieder, öffnet GlitchTip den Eintrag erneut.
+
+---
+
+## 5. Regeln, die nicht verletzt werden dürfen
+
+- **Datenschutz (M4):** `send_default_pii` bleibt `false`, `max_request_body_size` bleibt `never`, Log- und Cache-Breadcrumbs bleiben aus, `ErrorReportScrubber` bleibt in `before_send`. Kein Name, keine E-Mail, keine IP an GlitchTip — nur die interne User-ID. Neue Query-Parameter, die mitgehen sollen, nur über `ErrorReportScrubber::AllowedQueryParameters` (und dieselbe Liste in `error-reporting.js` / Umami-Filter).
+- **Keine variablen Zahlen in Fehlermeldungen**, wenn sie gruppiert werden sollen: GlitchTip gruppiert nach Text. `…after 20002 ms` und `…after 20001 ms` wurden drei getrennte Einträge (Issues 11, 13, 14). Variable Werte in den Kontext, nicht in die Meldung — oder einen Fingerprint setzen wie `ErrorSignal`.
+- **Vorübergehende Fremdfehler nicht einzeln melden.** Sonst flutet ein Google-Ausfall GlitchTip und verdeckt echte Fehler.
+- **Tests melden nie an GlitchTip** (Abschnitt 2). Neue Sentry-Tests nur mit `FakeGlitchTip`.
+- **Token nur lesend.** Für Schreibrechte (Auflösen per API) erst mit dem Betreiber sprechen.
+
+---
+
+## 6. Konfiguration
+
+| Variable | Wo | Wert |
+|---|---|---|
+| `SENTRY_LARAVEL_DSN` | `.env` | DSN Projekt 1 |
+| `SENTRY_BROWSER_DSN` | `.env` | DSN Projekt 2 (öffentlich, steht im HTML) |
+| `SENTRY_ENVIRONMENT` | `.env` | `production` (Live) bzw. `local` |
+| `SENTRY_RELEASE` / `APP_VERSION` | `.env` | leer lassen — kommt aus der Datei `RELEASE` |
+| `GLITCHTIP_API_TOKEN` | nur lokale `.env` | Lese-Token für Abschnitt 3 |
+
+**Release:** `App\Support\Monitoring\Release::current()` liest `APP_VERSION`
+bzw. `SENTRY_RELEASE`, sonst die Datei `RELEASE` im Projektwurzelverzeichnis.
+Die Datei entsteht beim Paketbau **auf dem Tag** (`git describe --tags --always > RELEASE`)
+und wird mit ausgeliefert. Nicht später auf einem anderen Stand neu erzeugen:
+dann steht z. B. `v1.1.0-3-gb1cec16` statt `v1.1.0` (so am 05./06.10. auf Live).
+Nach Änderung: `php artisan config:cache`.
+
+---
+
+## 7. Protokoll der Einträge
+
+Stand 06.10.2026 (Abruf per API). „Fix“ = im Code behoben; ausgeliefert mit dem nächsten Deploy.
+
+| ID | Projekt | Umgebung | Eintrag | Klasse | Ursache / Maßnahme | Stand |
+|---|---|---|---|---|---|---|
+| 1 | Server | local | GlitchTip-Test Server | Test | Anbindungstest 05.10. | auflösen |
+| 8 | Server | production | Deploy-Test v1.1.0 | Test | Deploy-Prüfung 05.10. | auflösen |
+| 3, 16 | Server | local | Queue weg | Test-Rauschen | `ExpireGrandfatheringTest` meldete an echte DSN | Fix `2f02acc`, auflösen |
+| 5, 17 | Server | local | Datenbank nicht erreichbar | Test-Rauschen | `PressReleaseSchedulingTest` | Fix `2f02acc`, auflösen |
+| 4 | Server | local | no such table: conversion_events | Test-Rauschen | SQLite im Test | Fix `2f02acc`, auflösen |
+| 6 | Server | local | Wasserstandsmarke steht auf 1 statt 0 | Test-Rauschen | Konkurrenztest der Aufruf-Aggregation | Fix `2f02acc`, auflösen |
+| 7 | Server | local | no role named `customer` | Test-Rauschen | Tests ohne Rollen-Seeder | Fix `2f02acc`, auflösen |
+| 9 | Server | production | Command "config:cachels" is not defined | Bedienfehler | Tippfehler auf dem Server | `dontReport` (16af374); auflösen |
+| 11, 13, 14 | Server | production | cURL error 28 … searchconsole.googleapis.com | Extern | Google URL Inspection hing; jeder Timeout einzeln gemeldet, Lauf 1¾ h | Fix a90f24a: Retry, Warnung statt Meldung, Abbruch nach 5 in Folge, 40-min-Budget; nach Deploy auflösen |
+| 12 | Server | production | URL Inspection API antwortet mit 500 | Extern | Google-interner Fehler | wie 11 |
+| 15 | Browser | production | ReferenceError: __ is not defined | **Bug** | `/register`: `:placeholder="__('…')"` an `` → Alpine wertet PHP-Helfer im Browser aus | Fix 16af374: `placeholder="{{ __('…') }}"`, Wächtertest `BladeAlpineTranslationGuardTest`; nach Deploy auflösen |
+| 10 | Browser | production | Error: [object Object] | Unklar | Promise-Ablehnung mit Objekt auf `businessportal24.com/de` | Erfassung verbessert (16af374, Typ + Inhalt); nach Deploy neues Auftreten abwarten |