---
title: Post-Mortem — Claude-Update-Ausfall + root→aria User-Migration (14.07.2026)
type: audit
tags: [post-mortem, infra, migration, root-cause]
date: 2026-07-14
status: aktiv
confidence: high
related: [[SOUL]], [[CORRECTIONS]], [[06-Daily/2026-07-14]], [[02-Wissen/aria-architektur-tief-2026-05-08]]
---

# Post-Mortem: Totalausfall 14.07.2026 — Claude-Update bricht root-Betrieb

> Auftrag Kais (TG 8584/8585), rein lesende Analyse. Alle Zahlen aus Logs/Configs dieses Servers,
> Quellenangabe jeweils dabei. Wo ich etwas nicht verifizieren konnte, steht das explizit.

## TL;DR

Ein nächtliches Claude-Code-Update (01:50 Uhr, auf 2.1.208) hat Aria gestoppt, weil die neue
Version Bypass-Permissions unter root verweigert. Es folgten **~1.050 erfolglose Neustarts in
10 Stunden ohne einen einzigen Alarm**, dann eine mehrstündige Not-Migration auf den User `aria`.
Das Update war nur der Auslöser. Die eigentliche Schwachstelle: **die Identität „Aria läuft als
root" war in jeder Schicht des Systems eingebrannt** (57 systemd-Units, 43 aktive Timer, Hooks,
Panel, Skripte, Credentials) — ein Ein-Zeilen-Politikwechsel im Upstream-Binary hat deshalb eine
Weltmigration unter Zeitdruck erzwungen. Kritischster offener Punkt heute: **`/home/aria/brain`
hat keinen funktionierenden Push-Pfad mehr — Brain-Änderungen seit der Migration erreichen
GitHub nicht.**

---

## 1. Root-Cause-Kette

### Timeline (rekonstruiert aus `/home/aria/logs/aria.log`, Datei-Timestamps, systemd)

| Zeit (UTC) | Ereignis | Beleg |
|---|---|---|
| 13.07. abends – 14.07. 01:20 | Aria (als root) läuft normal, letzte Aktivität 01:20:43 | aria.log: `auto-approved permission dialog` 00:09, 01:20 |
| **01:50:44** | Claude Code wird auf **2.1.208** aktualisiert (npm-global `/usr/lib/node_modules/@anthropic-ai/claude-code`) | mtime `package.json` = 2026-07-14 01:50:44 |
| **02:22:08** | Erster Neustart nach dem Update. Neue Version verweigert den Start (root + Bypass-Permissions). Crash-Loop beginnt: Start → „Session sofort beendet" → 30 s systemd-Restart → Start … | aria.log ab 02:22:08, Zyklus ~34 s |
| 02:22 – 12:20 | **Crash-Loop, ~10 Stunden, 1.059 Starts heute gesamt** (awk-Zählung `gestartet (PID`). Kein Alarm. | aria.log |
| ~12:20 – 15:07 | Kais greift ein; Migration root→aria beginnt (Loop-Ende, Lücke im Log) | aria.log: letzter Loop-Start 12:20:32, nächster Start 15:07:14 |
| 15:07 – 16:17 | Migrations-Folgefehler nacheinander: Pfade, `bun` fehlt im PATH (Fix 15:35, mtime `/usr/local/bin/bun`), Trust-Dialog blockiert (drei Sessions mit „Kein ❯ nach 120s": 15:30, 16:01, 16:10), zwei Sessions enden nach ~4–5 min | aria.log; Trust jetzt akzeptiert (`~/.claude.json`: `hasTrustDialogAccepted: true` für `/home/aria`) |
| **16:22:49** | Aktuelle Session startet (PID 4112072) und **läuft seitdem stabil** (>60 min, diese Analyse entsteht darin) | aria.log + laufender Prozess |

### Auslöser vs. Schwachstelle

**Auslöser (extern, nicht kontrollierbar):** Upstream-Politikänderung — Claude Code ≥2.1.x
verweigert `--dangerously-skip-permissions`/Bypass unter root. Das Update kam unbeaufsichtigt
nachts als npm-Global-Update. Solche Änderungen werden wieder passieren.

**Schwachstelle 1 (Design, die eigentliche Lehre):** *root war überall eingebrannt.* Die
Annahme „Aria = root" steckte nicht an einer Stelle, sondern in jeder Schicht:

- 57 systemd-Unit-Files mit `/root/aria`-Pfaden (140 Zeilen-Referenzen), User implizit root
- Claude-State unter `/root/.claude` (settings, hooks, plugins, sessions, trust)
- Wrapper, Hooks, Panel (`aria-control-v3`), Health-Server, Chat-Logger: alle Pfade absolut auf `/root/...`
- Credentials (git, GitHub-PAT, Telegram) nur in roots Welt hinterlegt

Konsequenz: Der User-Wechsel war kein Config-Flip (`User=aria` + fertig), sondern eine
Weltmigration — und jede vergessene Stelle wurde zum eigenen Folgeausfall (Pfade → bun → Trust →
Restart-Politik → Monitoring → Dashboard „offline" → Chat-Log leer → Brain-Sync tot).

**Schwachstelle 2 (Betrieb):** *10 Stunden Crash-Loop ohne Alarm.* Die Überwachung war Teil
derselben Welt, die kaputt war: `aria-watchdog`/`aria-telegram-watchdog` liefen als root-Timer
und haben den Zustand „Service startet 1.000× erfolglos" nie als Ausfall gemeldet. Es gibt
keinen Restart-Raten-Alarm und kein `OnFailure=`-Ping auf `aria.service` selbst. Erst ein
Mensch hat es morgens bemerkt.

---

## 2. Inventar verbliebener /root-Abhängigkeiten (Stand 14.07. ~19:15 CEST, nur gelesen)

### 2.1 systemd (größter Block)

- **57 von 107 aria-Unit-Files** referenzieren `/root/...` (140 Zeilen). Muster pro Service:
  `EnvironmentFile=-/root/aria/.env`, `EnvironmentFile=-/root/aria/scripts/.env.aria`,
  `ExecStart=... /root/aria/scripts/<skript>` — z. B. `aria-akp-*.service` (komplette
  AKP-Pipeline), `aria-git-sync.service`, `aria-watchdog.service`, `aria-memory-curator.service`.
  Vollliste (Datei+Zeile): `grep -n "/root" /etc/systemd/system/aria*.service /etc/systemd/system/aria*.timer`
- **43 aktive Timer — ausnahmslos alle** zeigen auf die `/root`-Welt und laufen als root
  (kein `User=` gesetzt). Kein einziger aktiver Timer arbeitet gegen `/home/aria`.
- Bereits migriert (Drop-in-Overrides vom 14.07.): `aria.service` (User=aria),
  `aria-chat-logger-sqlite.service`, `aria-health.service`.

### 2.2 Claude-Hooks (`/home/aria/.claude/hooks/`)

Die `.sh`-Hooks wurden beim Pfad-Fix migriert (Backups in `.pre-pathfix-bak/`), aber:

| Datei | Zeile | Problem |
|---|---|---|
| `gateguard.py` | 28 | Fallback `ARIA_MODE_FILE=/root/aria/state/mode` — Env-Var ist nirgends gesetzt, liest also die **alte** Mode-Datei. Funktioniert nur zufällig (ACL-Traversal seit heute); es existieren jetzt **zwei** Mode-Dateien (`/root/aria/state/mode` und `/home/aria/state/mode`, beide „auto") → Drift vorprogrammiert |
| `gateguard.py` | 133 | `GATEGUARD_STATE_DIR=/root/aria/state/gateguard` (Fallback) |
| `validators/markdown_wikilinks.py` | 31 | `VAULT_ROOT = /root/aria/brain` — **validiert Wikilinks gegen den alten Vault** |
| `validators/iteration_limit.py` | 23 | `STATE_FILE = /root/aria/state/validator-iterations.json` |
| `validators/markdown_frontmatter.py` | 5/14 | nur Doku-Strings, unkritisch |

### 2.3 Skripte (`/home/aria/scripts/`)

**126 Dateien mit 492 `/root/`-Referenzen** (grep, Backups ausgenommen). Solange die Timer
ohnehin `/root/aria/scripts/...` starten, ist das doppelt gemoppelt: die Home-Kopien sind
tot, die Root-Kopien laufen. Bei der Timer-Migration muss jedes Skript mitgeprüft werden
(viele haben `/root/...`-Defaults hart einkodiert, z. B. DB-Pfade, `SESSION_BACKUP`,
State-Dirs). Positiv-Beispiele von heute: `health-server.py` und `aria-chat-logger-sqlite.py`
sind bereits auf `/home/aria` umgestellt.

### 2.4 Control-Panel (`/root/projekte/aria-control/app/`)

Status-Pfad heute gefixt (`system.py`, `session.py`, `session_hero.py`, `actions.py` (tmux),
`logs.py`, `sse.py`, `usage.py` (tmux), neu `tmux.py`). **Verbleibend** (Datei → was):
`settings.py` (`brain_root=/root/aria/brain`, `ENV_FILES=/root/aria/.env`,
`legacy_token_file=/root/.aria-control-token`), `actions.py` (`_TASKS_FILE`,
`_QUICK_SKILLS_FILE`, `_RESTART_SCRIPT=/root/aria/scripts/aria-restart.sh`, `_BRAIN_REPO`),
`usage.py` (`_USAGE_MODULE_DIR=/root/aria/scripts`), dazu `workers.py`, `capabilities.py`,
`brain_search.py`, `integrations.py`, `insights.py`, `hardening.py`, `watch.py`, `tasks.py`
(je 1–8 Referenzen). Erfasst in **KAR-956**.

### 2.5 Kritisch: Brain-Sync ist faktisch tot

- `aria-git-sync.service` lief zuletzt 16:47 mit **Exit 0** — als root gegen `/root/aria/brain`
  (eigener, alter Baum; `readlink -f /root/aria` → `/root/aria`, KEIN Symlink).
- `/home/aria/brain`: letzter Commit `5801e6e` von **05:43** (vor der Migration), lokale
  Änderungen von heute **uncommitted**, und `git fetch` schlägt fehl
  („could not read Username") → **der User aria hat keine Git-Credentials**.
- Folge: Alles was Aria seit der Migration ins Brain schreibt (Daily-Log, dieses Post-Mortem)
  existiert nur lokal auf diesem VPS. Gleichzeitig „synct" root munter die Altwelt weiter —
  falls dort noch Commits entstehen, droht Divergenz auf GitHub.

### 2.6 Sonstiges

- **GitHub-PAT** liegt im Klartext in `~/.claude/settings.json` (`env.GITHUB_PERSONAL_ACCESS_TOKEN`)
  und ist **ungültig** (API-Test: „Bad credentials") → rotieren, aus settings.json raus, nur `.env`.
- Doku/Identity: `/home/aria/CLAUDE.md` und diverse Brain-Notes referenzieren noch
  `/root/aria/...`-Pfade (z. B. BOOTSTRAP-Verweis) — verwirrt jede künftige Session.
- Session-Start-Hook meldet: `LINEAR_API_KEY` fehlt in Env, brain-health-Skript fehlt am
  erwarteten Pfad — beides Symptome derselben Pfad-/Welt-Spaltung.

---

## 3. Fragilitäts-Bewertung (Single Points of Failure)

**Beim nächsten Claude-Code-Update:**
- Update ist npm-global und ungepinnt; nichts verhindert einen erneuten Breaking-Change um
  02 Uhr nachts. Es gibt keinen Canary-Test („startet die neue Version überhaupt als aria?")
  vor dem Wirksamwerden. Risiko unverändert **hoch** — nur die root-Sperre selbst ist durch
  die Migration entschärft.
- Neu-hinzugekommenes Risiko: `aria.service` hat jetzt `StartLimitIntervalSec=300` /
  `StartLimitBurst=5`. Ein künftiger Crash-Loop (34-s-Zyklus) erreicht das Limit nach
  ~3 Minuten — dann bleibt der Dienst **dauerhaft tot** statt endlos neu zu starten. Ohne
  `OnFailure=`-Alarm ist das die stillere Variante des heutigen Ausfalls.

**Beim nächsten Server-Reboot** (steht an: Kernel 6.8.0-107 läuft, 6.8.0-134 installiert):
- 43 root-Timer starten wieder gegen die Altwelt → Altwelt lebt weiter, Drift wächst.
- `aria.service` selbst sollte sauber hochkommen (Trust akzeptiert, Unit korrekt) — aber das
  wurde noch nie durch einen Reboot bewiesen. Watchdogs sind aus; wenn irgendwas hängt,
  merkt es wieder niemand.

**Wenn eine Config neu geschrieben wird:**
- `~/.claude.json` / `settings.json` werden von Claude Code selbst umgeschrieben; die
  Plugin-/Marketplace-Registrierung hat heute schon einmal Pfade verloren. Jeder Mechanismus,
  der Configs aus der Altwelt „wiederherstellt" (Backups, Sync-Skripte, `aria-arsenal-sync`
  läuft als root!), kann `/root`-Pfade zurückbringen. Es gibt keinen Guard, der das erkennt.

**Strukturell:**
- Monitoring prüfte bis heute den falschen Dienst/die falsche Welt; Watchdogs sind aktuell
  komplett aus (enabled/inactive) → **kein einziges aktives Alarm-Signal** für Aria-Ausfall.
- Chat-Logging (Kontext-Brücke zwischen Sessions) ist tot (KAR-955) → jeder Neustart verliert
  mehr Kontext als früher.
- Zwei parallele Welten mit identisch benannten Dateien sind der perfekte Nährboden für
  „warum greift mein Fix nicht?"-Stunden (heute mehrfach passiert: health-server, mode-Datei).

**Zum „~5-Minuten-Selbstbeende-Zyklus":** Aus dem Log heute: zwischen 15:07 und 16:22 gab es
8 Starts; drei blieben ohne ❯-Prompt hängen (Trust-Dialog), zwei endeten ~4–5 min nach Start
(16:01→16:05:09, 16:17:43-Prompt→16:22:18). **Seit 16:22:49 läuft die Session stabil** —
der Zyklus ist aktuell NICHT aktiv. Die Exit-Ursache der beiden Selbstbeender ist aus den
vorhandenen Logs nicht bestimmbar: `aria-stderr.log` ist 0 Bytes, der Wrapper loggt keinen
Exit-Code, journalctl ist für den aria-User beschnitten (keine adm-Gruppe). Plausibelste
Kandidaten: Trust-Dialog-Abbrüche und manuelle Restarts während der Migrations-Fixes.
→ Maßnahme 6 unten macht das beim nächsten Mal diagnostizierbar statt Rätselraten.

---

## 4. Präventionsmaßnahmen (priorisiert, konkret — NICHT umgesetzt, nur vorgeschlagen)

**P0-1 — Brain-Push-Pfad wiederherstellen (Datenverlust-Risiko, zuerst):**
Git-Credentials für User aria einrichten (Deploy-Key für `KADiCon/aria-brain` oder PAT in
`~/.git-credentials`, chmod 600), `aria-git-sync` als aria-Unit auf `/home/aria/brain`
umziehen (Drop-in wie beim Chat-Logger), root-git-sync-Timer stoppen. Verifikation:
Test-Commit → auf GitHub sichtbar. Vorher prüfen ob GitHub-main inzwischen Commits aus der
Altwelt hat (Divergenz auflösen, nicht überschreiben).

**P0-2 — Update-Kontrolle für Claude Code:**
(a) Auto-Update aus: `DISABLE_AUTOUPDATER=1` in die Service-Env; Version pinnen
(`npm i -g @anthropic-ai/claude-code@2.1.208` bewusst, nicht nächtlich-automatisch).
(b) Update-Skript mit Canary-Gate: neue Version installieren → als aria in Wegwerf-tmux
`claude --version` + Mini-Session-Smoke-Test → erst bei Erfolg `aria.service` restarten,
sonst Rollback auf gepinnte Version + Telegram-Ping. Klären: was hat um 01:50 das Update
ausgeführt (Claude-eigener Updater? Cron? unattended)? — der Mechanismus muss unter Kontrolle.

**P1-3 — Ausfall-Alarm unabhängig von der Aria-Welt:**
`OnFailure=aria-failure-notify@%n.service` auf `aria.service` (Template existiert bereits,
KAR-877!) + ein Restart-Raten-Check (deterministisch, KAR-684-Muster): >3 Starts in 10 min →
Telegram-Ping. Dazu Watchdog-Timer wieder aktivieren, nachdem sie auf die neue Welt zeigen.
Damit wäre der heutige 10-Stunden-Blindflug eine 10-Minuten-Störung gewesen.

**P1-4 — Zentrale Pfad-Quelle statt hartcodiert:**
Eine Datei `/etc/aria/aria.env` (oder `/home/aria/.env` als Single Source) mit `ARIA_HOME`,
`ARIA_BRAIN`, `ARIA_STATE`, `ARIA_DB` — Units konsumieren sie via `EnvironmentFile=`,
Python/Bash-Skripte via `os.environ[...]` mit **hartem Fehler** statt /root-Fallback
(Lehre aus LRN-20260506-006: falsche Defaults verstecken Fehler). Neue Regel für jeden
Code-Write: kein absoluter `/root`- oder `/home/aria`-Pfad außerhalb der Env-Definition.

**P1-5 — Timer-Fleet-Migration mit Inventar-Tabelle (= KAR-956):**
Pro Unit: braucht es sie noch? → Drop-in `User=aria` + Pfade → Funktionstest → Altwelt-Skript
stilllegen. Staged (5–10 pro Tag), nicht big-bang. Inventar-Grundlage liegt schon vor
(Scratchpad-Grep dieser Analyse, in KAR-956 referenziert).

**P2-6 — Wrapper diagnostizierbar machen:**
`wrapper.sh`: Exit-Code von claude loggen (`wait $PID; log "Claude Exit-Code $?"`),
stderr-Log rotieren statt 0-Byte-Leiche, und bei „Session sofort beendet" die letzten
20 Pane-Zeilen mitloggen. Kostet 5 Zeilen, hätte heute Stunden gespart.

**P2-7 — Migration als Runbook/Skript festhalten:**
Die heutige Migration ist nirgends reproduzierbar dokumentiert. Ein idempotentes
`migrate-to-user.sh` (oder mind. Schritt-Checkliste im Brain) macht den nächsten Vorfall
(neuer Server, Restore, zweite Instanz) zu Routine statt Archäologie.

**P2-8 — Secrets-Hygiene:**
GitHub-PAT rotieren (aktueller ist eh invalid), aus `settings.json` entfernen, in `.env`
(chmod 600) führen; `aria-verify` um Check „kein Token in settings.json / kein /root in
aktiven Units+Hooks" erweitern — das ist der Config-Rückfall-Guard aus Abschnitt 3.

---

## 5. Offene Baustellen aus heute (Checkliste — Stand nach Autonomie-Durchlauf 19:50)

- [x] **P0: Brain-Push für `/home/aria/brain`** — GELÖST 17:16 UTC: Root-Skript gelaufen (Token validiert als KADiCon + übernommen, git-sync als User aria), Push verifiziert (Commit `69f7ae0` auf origin/main, fetch als aria ok). Altwelt war clean (0 dirty/0 unpushed), wird ab jetzt bewusst nicht mehr gesynct
- [x] Wrapper-Exit-Logging (Maßnahme 6): `wrapper.sh` loggt jetzt Exit-Code, stderr-Tail, Pane-Snapshot (greift ab nächstem Service-Restart). 5-Min-Zyklus seit 16:22:49 nicht mehr aufgetreten — weiter beobachten
- [x] Supabase-MCP-OAuth: verbunden 17:22 UTC (erster Link hatte stale client_id; zweiter Flow + complete_authentication mit Kais' Callback-URL). Sichtbar: private-ops-prod + kadi-v2 (Aria-DB qiamvqfasz… und Kadi-V1 nicht in der Liste — andere Org oder gelöscht, bei Bedarf klären)
- [~] Panel-Restpfade: `settings.py` (brain_root, ENV_FILES home-first) heute zusätzlich gefixt + verifiziert; Rest (`actions.py`-Dateipfade, `workers.py`, `capabilities.py` u. a.) → **KAR-956**
- [x] Monitoring-Basisschutz: OnFailure auf `aria.service` + Notify-Template mit /home-Pfaden installiert (17:16, greift ab nächstem Restart); Watchdog-Timer bleiben bewusst AUS bis sie auf die neue Welt zeigen (sonst kämpfen sie gegen die aria-Session) → KAR-956
- [ ] **43 aktive Timer laufen als root gegen die /root-Altwelt** → **KAR-956** (Maßnahme 5)
- [~] GitHub-Token: toter PAT aus `~/.claude/settings.json` entfernt (19:45); gültiges Token wird vom Root-Skript nach Validierung aus /root/aria/.env übernommen — falls auch das invalid ist: neues PAT nötig
- [ ] Chat-Logging tot (Pane-Scraping greift nicht mehr, Plugin-Patch weg) → **KAR-955**
- [x] `gateguard.py`-Mode-Fallback + `markdown_wikilinks.py`-VAULT_ROOT + `iteration_limit.py`-STATE_FILE auf /home/aria umgestellt (19:40, Backups vorhanden)
- [x] Zwei Mode-Dateien: gateguard liest jetzt `/home/aria/state/mode`; die Root-Kopie ist damit funktionslos (Entsorgung beim Altwelt-Sweep)
- [ ] `/root`-Altwelt nach Sweep einfrieren/archivieren (nur mit Kais-Go, nichts löschen) → **KAR-956**
- [ ] LINEAR_API_KEY fehlt in aria-Env (Session-Start-Hook meldet es; Linear geht derzeit nur via MCP-Plugin)
- [ ] DISABLE_AUTOUPDATER=1 im Root-Skript enthalten (greift ab nächstem aria.service-Restart); Update-Canary-Skript (Maßnahme 2b) noch zu bauen; **offen: welcher Mechanismus hat um 01:50 aktualisiert?** (root-crontab für aria-User nicht einsehbar)
- [ ] Ausstehender Kernel-Reboot (6.8.0-134 installiert, -107 läuft) — erst nach P0/P1-Punkten, dann als bewusster Failover-Test

Legende: [x] erledigt · [~] vorbereitet/teilweise, wartet auf Root-Skript bzw. Kais · [ ] offen

## Anhang: Verwendete Kommandos (alle read-only)

`grep/awk` auf `/home/aria/logs/aria.log` · `stat` auf package.json/bun · `systemctl cat/show/list-timers/list-unit-files` ·
`grep -n "/root"` über `/etc/systemd/system/aria*`, `~/.claude/**`, `~/scripts/**`, Panel-`app/` ·
`git log/status/fetch` (fetch schlug fehl = Befund) · `sqlite3 SELECT` · `curl 127.0.0.1:9090/health/full` ·
Rohdaten-Listen: Scratchpad `root-refs-units.txt`, `timer-welten.txt` (Session-temporär; Kernzahlen stehen oben im Text)
