Hinweis zu Pfaden: Alle absoluten Dateisystempfade in diesem Artikel sind anonymisiert. Die Verzeichnishierarchie und Logik entsprechen dem realen Vorfall; die konkreten Namen (z.B.
~/projects/private/) sind generisch gewählt.
"Claude Code ist eine Zeitmaschine" — das klingt nach KI-Marketing. Ich hätte es selbst überlesen, wenn ich es nicht gebraucht hätte. Aber diesen Satz schreibe ich jetzt, nicht weil ich Claude Code verkaufen will, sondern weil er mir mein privates Blog-Repo gerettet hat. Fast. Zu 84 Prozent, um genau zu sein.
Jeder Entwickler kennt die Regel: Backup. Ich rate meinen Kolleg:innen dazu. Ich rate Freunden dazu. Mein privates Blog-Repo lag lokal, ohne Remote, ohne TimeMachine. Das ist die Art Heuchelei, die man sich selbst lange verzeiht — bis sie einen einholt. Bei mir hat sie mich am 4. April 2026 eingeholt, um 01:38:23 Uhr, als ich ein einzelnes Zeichen zu viel getippt habe.
Am Ende hatte ich 84 Prozent des Repos zurück. Aber nicht, weil ich ein Backup hatte. Sondern weil Claude Code ein Nebenprodukt erzeugt, das sich — wenn man es nicht dafür gebaut hat, aber trotzdem hat — wie ein Backup verhält. Das ist ein feiner, aber wichtiger Unterschied. Und genau dieser Unterschied ist das Thema dieses Artikels.
Der Fehler als Cleanup-Kaskade
Um den Fehler einzuordnen: Ich arbeite seit einigen Monaten an claude-forge, meinem eigenen Lernprojekt parallel zur SAGE/SageKit-Entwicklung (einem größeren KI-Werkzeugsystem, an dem ich nebenbei baue). Zwei ClaudeSDKClient-Instanzen — eine simuliert einen Entwickler, die andere ist Claude Code selbst — entwickeln im Dialog autonom Projekte. Als Experimentier-Setup sehr lehrreich. Als Erzeuger von Artefakten an schwer vorhersehbaren Orten im Dateisystem auch.
An diesem Freitagabend hatte claude-forge seine Test-Outputs nicht wie konfiguriert in ~/projects/private/claude-forge-test/ abgelegt, sondern über mehrere Stellen verteilt: ~/projects/tui-calc, ~/projects/.superpowers/…, ~/projects/private/{calculator,tui-calculator,calc-tui}. Vor dem Schlafengehen wollte ich aufräumen.
Die Zsh-History zeigt die Sequenz in der Nacht zum Samstag, 4. April, auf die Sekunde genau:
01:37:15—rm -Rf ~/projects/tui-calc01:38:10—rm -Rf ~/projects/.superpowers01:38:23—rm -Rf ~/projects/private
Dreizehn Sekunden zwischen dem zweiten und dem dritten Befehl. Der dritte war falsch. Geplant war rm -Rf ~/projects/private/calc-tui — der Pfad rutschte eine Ebene zu hoch. Der Fehler war nicht der erste rm -rf des Abends. Er war der sechste einer Aufräum-Sequenz in rund 90 Sekunden. Ich war zu geübt geworden. Routine schlägt Aufmerksamkeit, das ist bekannt, und trotzdem unterschätzt man es regelmäßig.
Das Besondere an diesem Befehl: Er hat nicht alles mitgenommen. Ich habe ihn — vermutlich instinktiv — nach Sekundenbruchteilen abgebrochen. Zwei Dateien haben überlebt, und zwar genau die zwei, die am wenigsten dramatisch sind: CLAUDE.md und generate-feature-images.py. Kein einziger Artikel, kein einziger Commit-Hash, kein Metadata-File. Zwei Hilfs-Dateien, wie ein kosmischer Witz.
Bestandsaufnahme am nächsten Morgen
Der erste Reflex nach dem Abbruch war nicht Panik, sondern Suche — aber nicht am MacBook. Die zwölf Minuten zwischen Ctrl+C und tmutil listlocalsnapshots verbrachte ich auf dem iPad mit der Claude-App. Teils, um überhaupt herauszufinden, was bei einem frischen rm -rf auf APFS noch rettbar ist. Vor allem aber, um jede weitere Write-Operation auf dem MacBook zu vermeiden, solange ich nicht wusste, welche gelöschten Blöcke noch zu retten waren. Die iPad-Antwort bestätigte den Instinkt: Stopp! Schreibe nichts mehr auf das betroffene Volume! Erst um 01:48 Uhr, nachdem die Claude-App auf dem iPad eine grobe Richtung vorgegeben hatte, ging es zurück ans Terminal — mit tmutil listlocalsnapshots /. Kurz danach: tmutil listlocalsnapshots /System/Volumes/Data, dann diskutil info /, dann eine Spotlight-Suche per mdfind über Source-Code-Artefakte. Um 01:50 Uhr steckte ein USB-Stick namens TRANSER im Rechner, auf den ich vorsichtshalber alles kopierte, was Spotlight noch unter ~/projects/private/ kannte. So professionell wie der erste Reflex aussah, so klar war nach zwanzig Minuten: Es gab keine aktive TimeMachine-Konfiguration. Es gab keine brauchbaren lokalen Snapshots. Es gab einen USB-Stick mit ein paar geretteten Blobs.
Samstagnachmittag, nach ein paar Stunden Schlaf, das nüchterne Bild:
.git/indexintakt: rund 60 KB, 490 Dateien referenziert.git/objects/teilweise: 132 lose Blobs überlebt, 59 davon als valide Git-Objekte importierbar.git/pack/: weg, komplett- 34 Commit-Objekte überlebt, das Reflog mit 260 Einträgen intakt
Um 15:14 Uhr startete ich eine Claude-Code-Session, die später den Custom-Titel "recovery" bekommen sollte, und formulierte das Problem so, wie es in der JSONL jetzt wörtlich dokumentiert ist: "ich hab gestern einen fehler beganngen und hab rm -Rf ~/projects/private/ ausgeführt. Hab gleich sofort abgebrochen. Aber die Blogs sind weg und das .git Verzeichnis ist korrupt." Inklusive Tippfehler — das ist der Zustand, in dem man eine Recovery-Session startet.
Die Claude-Analyse danach, paraphrasiert: Die Index-Datei kennt 490 Dateien. Aber 419 von 463 Content-Blobs fehlen. Die geretteten Blobs haben mit den fehlenden keine Überlappung — es sind alte Versionen, nicht der aktuelle Stand. Der Index weiß also präzise, was fehlt. Genau das fehlt auch.
Das ist die Sorte Befund, die an einem Samstagnachmittag eine klare Wirkung hat. Man hat eine Inventur, aber keine Ware.
Schuster-Kinder-Moment
Hier sitze ich jetzt also, der Entwickler, der anderen zu Backups rät, und versuche, aus einem halben .git-Verzeichnis vier Monate Arbeit zu rekonstruieren: 15 Blog-Artikel, jeweils in bis zu fünf Sprach- und Plattform-Varianten (svenpoeche DE+EN, MF-Blog DE, Medium EN, DEV.to EN), dazu über 55 Excalidraw-Diagramme und die dazugehörigen Feature-Images. Das ist keine Hobby-Sammlung mehr. Das ist eine Publishing-Infrastruktur.
Die ehrliche Überlegung lautete an diesem Punkt: Aufgeben. Die publizierten Artikel sind auf svenpoeche.de, auf dem Mayflower-Blog, auf Medium und auf DEV.to. Die kann ich zurückkopieren. Aber was ich nicht zurückkopieren kann: Drafts, Metadata-Dateien, Excalidraw-Quellen, halbfertige Übersetzungen, Research-Notizen. All das, was zwischen "Idee" und "publiziert" entsteht und nirgendwo anders existiert als auf der Festplatte, die ich gerade teilweise gelöscht habe.
Etwa an diesem Punkt kam die zweite Welle Verlegenheit dazu, die weniger mit dem Vorfall selbst zu tun hatte als mit der Rolle, die ich als Senior-Entwickler sonst einnehme: Ich bin derjenige, der im Team bei jedem neuen Projekt anspricht, ob das Backup-Konzept steht. Nicht in einer belehrenden Tonlage, einfach als selbstverständliche Frage. Die gleiche Frage, die in meinem Fall seit Monaten eine unehrliche Antwort bekommen hätte.
Dann sagte ich einen Satz zu Claude, den ich rückblickend als den Wendepunkt bezeichnen würde — kein Geistesblitz, sondern eine Routine-Anweisung, die ich in anderen Kontexten schon mehrfach erteilt hatte, nur nie mit so hohem Einsatz:
Der Jackpot-Satz
Der Satz war, wörtlich:
"Durch suche doch einfach deine JSONL Dateien"
Die Formulierung trägt einen Tippfehler — ein Leerzeichen zu viel, gemeint war "Durchsuche". Ich habe den Wortlaut nachträglich in der JSONL verifiziert. Es war nicht als Geistesblitz gedacht, sondern als nebenbei geäußerte Vermutung. Die Grundidee dahinter war trivial: Claude Code speichert jede Konversation lokal als JSONL-Datei. Jeder Write-Tool-Aufruf enthält den vollständigen Datei-Inhalt als Parameter. Jeder Edit enthält das zu ersetzende und das neue Fragment. Bash-Heredocs enthalten den Inhalt als Teil des Befehls. Wenn ich Claude Code seit Dezember für meine Blog-Artikel nutze, dann müsste der komplette Text jeder Datei, die Claude je für mich geschrieben hat, in diesen JSONL-Logs stehen.
Jede Datei, die Claude Code jemals für mich geschrieben hat, ist im JSONL-Log meiner Sessions archiviert — nicht als bewusstes Backup, sondern als Nebenprodukt der Conversation-Speicherung.
Claudes Reaktion, paraphrasiert: Im Projekt-Verzeichnis liegen 247 JSONL-Dateien. 92 enthalten Write-Aufrufe, 54 enthalten Edits. Ich fange mit der Extraktion an.
Das hätte eine 30-Minuten-Recovery werden können, wenn JSONL die einzige Quelle geblieben wäre. Es war nicht so. Erst beim Abgleich zwischen dem JSONL-Ertrag und dem, was der .git/index als "gesamt" kannte, zeigte sich, wie viel JSONL nicht abdeckt — Binärdateien grundsätzlich nicht, ältere Artikel aus der Roo-Code-Ära nicht, per externen Tools erzeugte Assets nicht. In den folgenden Stunden kamen fünf weitere Quellen dazu, und in den Tagen danach einige Nacharbeiten. Die gesamte Recovery-Session lief von Samstag, 4. April, bis zum 16. April — zwölf Tage, in denen ich zwischen Editor, Terminal und Claude hin und her gesprungen bin. Hier fängt der technische Teil an.
Die JSONL-Zeitmaschine: Wie sie existiert
Claude Code legt jede Session als JSONL-Datei unter ~/.claude/projects/<path-slug>/<session-id>.jsonl ab. Jede Zeile ist ein JSON-Event: User-Message, Assistant-Response, Tool-Call, Tool-Result. Für Recovery-Zwecke sind drei Event-Typen relevant: Write (voller Dateiinhalt als content-Parameter), Edit (altes und neues Fragment als old_string/new_string), und Bash-Commands mit Heredoc-Syntax (Inhalt im Befehls-String). Nicht persistent sind Read-Outputs — die stehen zwar im Tool-Result, aber Claude liest beim Session-Replay nicht denselben Zustand, also braucht man die nicht für Recovery. Binärdateien landen gar nicht erst in JSONL.
Die Retention wird über das Setting cleanupPeriodDays in ~/.claude/settings.json gesteuert. Default: 30 Tage. Wer das nicht anpasst, verliert nach einem Monat alles.
Dass die JSONL-Logs vom 4. April überhaupt noch existierten, war kein Zufall — aber auch kein Backup-Planungs-Move. Claude Code räumt standardmäßig nach 30 Tagen auf. Ich hatte cleanupPeriodDays Monate vorher auf ein Vielfaches erhöht, aus einem ganz anderen Grund: Ich kam von Roo Code, wo Sessions persistent blieben. Als ich in Claude Code eine alte Session suchte und sie schlicht nicht mehr existierte, war die Frustration groß genug, den Wert sofort drastisch hochzusetzen. Ein früheres Ich hat über einen Tool-Wechsel geflucht — und damit einem späteren Ich das Repo gerettet.
Das ist die Art kausale Kette, die sich rückblickend erzählen lässt, als wäre sie geplant gewesen. War sie nicht. Ohne den Roo-Code-Frust hätte ich den Default-Wert nicht angetastet, und dann wären die JSONLs der entscheidenden Dezember-bis-März-Phase beim Vorfall am 4. April bereits Geschichte gewesen. Das ändert die Gleichung fundamental: Nicht "Claude Code hat ein Backup, das dich rettet", sondern "Claude Code hat ein Nebenprodukt, das dich rettet, wenn du zufällig zwei Monate vorher aus einem völlig anderen Grund ein Retention-Setting hochgeschraubt hast". Das ist keine Empfehlung an Anthropic, das Feature zu verkaufen. Das ist eine Warnung, sich darauf nicht zu verlassen.
Quelle 1: JSONL-Conversation-Logs
Die wichtigste Quelle. Die Methode war konzeptionell einfach: Alle JSONL-Dateien im Projekt-Verzeichnis scannen, Write- und Edit-Tool-Aufrufe extrahieren, nach Ziel-Pfad gruppieren, pro Pfad die zeitlich letzte Version behalten. Die ISO-8601-Timestamps in den Events erlauben lexikografische Sortierung, was chronologischer Sortierung entspricht.
Zusätzlich zur Haupt-Session hatte ich während der vergangenen Monate regelmäßig Git-Worktrees für parallele Artikel-Arbeit angelegt. Jeder Worktree bekommt in Claude Code einen eigenen Projekt-Slug (der Pfad-Slug enthält den Worktree-Pfad), also eigene JSONL-Dateien. Insgesamt 81 Dateien aus parallelen Worktree-Sessions, die ich beim ersten Durchlauf übersehen hätte, wenn das Skript nur im Haupt-Projekt-Slug gesucht hätte.
Das Kern-Pattern des Extractors:
import json
from pathlib import Path
from collections import defaultdict
def extract_writes(jsonl_path: Path) -> dict[str, str]:
"""Returns {file_path: latest_content} from a Claude Code JSONL session."""
by_path: dict[str, list[tuple[str, str]]] = defaultdict(list)
with open(jsonl_path, "r", encoding="utf-8") as f:
for line in f:
try:
event = json.loads(line)
except json.JSONDecodeError:
continue
if event.get("type") != "assistant":
continue
ts = event.get("timestamp", "")
for block in event.get("message", {}).get("content", []) or []:
if not isinstance(block, dict) or block.get("type") != "tool_use":
continue
tool = block.get("name")
inp = block.get("input", {}) or {}
if tool == "Write" and inp.get("file_path") and inp.get("content") is not None:
by_path[inp["file_path"]].append((ts, inp["content"]))
elif tool == "Edit" and inp.get("file_path") and inp.get("new_string") is not None:
by_path[inp["file_path"]].append((ts, inp["new_string"]))
return {path: sorted(entries)[-1][1] for path, entries in by_path.items()}
Edge-Cases, die man beim ersten Durchlauf unterschätzt: Lange Datei-Inhalte, die Claude via Heredoc in einen Bash-Befehl pipt — die stehen nicht im Write-Block, sondern im Bash-Block als Teil des command-Strings. Edit-Aufrufe liefern nur ein Fragment, kein ganzes File; wer ausschließlich Edits hat, muss Diffs über mehrere Events verketten, sonst fehlt der Rahmen. Und wer keinen deterministischen Tiebreaker zwischen gleichzeitigen Events hat (selten, aber möglich), bekommt die falsche Version zurück.
Der Ertrag: 206 Dateien aus der Haupt-Session, 81 aus den Worktree-Sessions, insgesamt 287 Dateien rekonstruiert — größtenteils Blog-Artikel, Metadata-Files und Skripte.
★ Insight ──────────────────────────────────────────────────────────────────Die JSONL-Logs sind ein unbeabsichtigter Audit-Trail. Jede Datei, die Claude Code je für dich geschrieben hat, steht dort im Klartext. Das hat Implikationen, die über Recovery hinausgehen — für Security, für Compliance, für die Frage, was ein AI-Coding-Agent eigentlich an sichtbarer Historie hinterlässt.
────────────────────────────────────────────────────────────────────────────
Quelle 2: Der Git-Reflog-Trick
Parallel zur JSONL-Extraktion schaute ich mir den übrig gebliebenen Rest von .git/ genauer an. Die Pack-Datei war weg, aber .git/logs/HEAD hatte die Zerstörung überstanden — als reine Textdatei ist das Reflog auch dann noch lesbar, wenn Objekt-Datenbanken Lücken haben. 260 Einträge standen drin, Zeitraum 2025-12-12 bis 2026-04-03 12:37 Uhr (der letzte Commit vor dem rm).
Reflog liefert Commit-Hashes, Zeitstempel und Messages — nicht die eigentlichen Blobs. Aber für die Rekonstruktion von status.md, Commit-Reihenfolgen und projekt-übergreifender Historie war das Gold wert: 203 Commits ließen sich vollständig rekonstruieren, inklusive Parent-Kette, als sauber sortierbare Liste.
Das Kern-Kommando dafür:
git reflog --format='%H %at %gs' \
| while read hash time msg; do
printf '%s %s %s\n' "$(date -r "$time" '+%Y-%m-%d %H:%M:%S')" "$hash" "$msg"
done
Die Einschränkung ist so offensichtlich wie wichtig: Reflog referenziert Commit-Objekte. Wenn die Objekte in der Pack-Datei lagen und die Pack-Datei weg ist, liefert dir das Reflog zwar die Message, aber kein Diff. Die Blob-Inhalte mussten aus den anderen Quellen kommen. Trotzdem: Der Reflog beantwortete verlässlich die Frage "was hatte ich überhaupt mal?" — und das ist für Recovery oft der halbe Weg.
★ Insight ──────────────────────────────────────────────────────────────────Git speichert Commit-Informationen an drei Orten:
objects/,refs/,logs/. Wer nurobjects/als "die Wahrheit" denkt, unterschätzt das Reflog. Es ist ein separater Log, kein Index auf Objects — und überlebt Katastrophen, die den Objektspeicher treffen.
────────────────────────────────────────────────────────────────────────────
Quelle 3: file-history
Parallel zu den JSONL-Logs legt Claude Code unter ~/.claude/file-history/ versionierte Snapshots von Dateien an, die es anfasst. Die Dateinamen sind Hash-basiert mit @v1, @v2-Suffixen — jede Version separat, unabhängig von der JSONL-Conversation-Sicht.
Der Nutzen ist anders als bei JSONL: JSONL ist die Conversation-Sicht — jedes Write/Edit als Event mit Timestamp. file-history ist die Datei-Sicht — pro Datei die Versionen. Überlappt nicht 1:1, denn file-history enthält auch Stände, die aus Read-Kontexten stammen oder als Zwischenschritt in mehrstufigen Edits angelegt wurden. 9.377 Snapshots insgesamt auf meinem System, gefiltert auf blog-relevante Pfade: 141 Dateien.
Der größte Teil davon überschnitt sich mit dem JSONL-Ertrag und bestätigte die dortigen Inhalte — das war der eigentliche Wert. Bei Rekonstruktion ist "zwei Quellen sagen dasselbe" die einfachste Form von Vertrauen, die man kriegen kann. Ein paar Stände kamen aus file-history, die ich in JSONL nicht gefunden hatte: Zwischenstände, die nur als Read-Kontext einen Snapshot bekommen hatten, ohne dass sie als Write aus der Session entstanden waren. Diese Lücke zwischen "Claude schreibt es" und "Claude fasst es an" hätte ich aus JSONL allein nicht geschlossen.
Quelle 4: Publii-DB als unerwartete Backup-Schicht
Publii, meine lokale Blog-Publishing-Software, speichert alle Posts in einer SQLite-Datenbank: ~/Documents/Publii/sites/sven-poeche/input/db.sqlite. Medien liegen separat unter input/media/. Weder das eine noch das andere lag unter ~/projects/private/ — also hatte der rm -Rf diese Schicht nicht einmal gestreift.
Die Extraktion war ein SQL-Einzeiler:
SELECT slug, title, text, status,
datetime(created_at / 1000, 'unixepoch') AS created,
datetime(modified_at / 1000, 'unixepoch') AS modified
FROM posts
WHERE status IN ('published', 'draft', 'published,is-page')
ORDER BY modified DESC;
15 Artikel in zwei Sprachen ergaben 30 Einträge in der posts-Tabelle, plus drei statische Pages (About, Impressum, Kontakt). Plus sieben Drafts, die zum Teil nicht mehr aktuell waren. Recovery-relevant im engeren Sinn: die 30 Artikel-Einträge als direkte Markdown-Quelle.
Die Assets waren die zweite gute Nachricht: 69 von 71 Feature-Images und Excalidraw-PNGs aus dem Media-Ordner waren komplett. Die Einschränkung bei dieser Quelle liegt auf der Hand — Publii speichert nur, was publiziert wurde. Drafts, die ich nicht über Publii gepflegt habe, sind nicht drin. Metadata-Dateien auch nicht. Für die Produktions-Seite aber: nahezu vollständig.
Quelle 5: PNG → Excalidraw via Claude Vision
32 Excalidraw-Diagramme fehlten. Die waren nicht in JSONL (die Excalidraw-JSONs wurden mit einem externen Tool erzeugt, nicht über Claude Codes Write), nicht in file-history, nicht in Publii. Nur die PNG-Renderings lagen als Blog-Assets vor — also das, was Publii aus den Excalidraw-Quellen exportiert hatte.
Der Ansatz war ehrlich gesagt ein Experiment, das ich nicht für so tragfähig gehalten hätte: Claude Vision auf die PNGs ansetzen und die JSON-Quelle rekonstruieren lassen. Drei parallele Subagents, jeder bekam einen Batch PNGs und einen Prompt in dieser Richtung:
"Dieses PNG zeigt ein Excalidraw-Diagramm. Identifiziere alle Elemente (Rechtecke, Pfeile, Text, Farben, Positionen). Erzeuge eine valide Excalidraw-JSON-Datei, die das Diagramm äquivalent reproduziert."
Der Ertrag war besser als erwartet. 11 englische Diagramme ließen sich direkt aus PNGs rekonstruieren. Die 21 deutschen Varianten habe ich nicht nochmal per Vision verarbeitet — die deutschen und englischen Diagramme haben dieselbe Struktur und dieselben Positionen, nur die Labels sind unterschiedlich. Also: englische JSON nehmen, Text-Labels übersetzen, deutsche JSON zurückschreiben. Das macht Claude als Text-Aufgabe in Sekunden. Zusammen: 32 rekonstruierte Diagramme. Zusammen mit den JSONL- und file-history-Funden kam ich auf 55 von 56.
Qualitäts-Einordnung, damit keine Illusionen entstehen: Die rekonstruierten Diagramme sind strukturell äquivalent, nicht bitgenau. Element-Positionen weichen um ein paar Pixel ab, Pfeil-Krümmungen sind nicht identisch, Font-Metriken unterscheiden sich leicht. Für ein Blog-Diagramm, dessen Zweck es ist, eine Information zu transportieren: vollkommen ausreichend.
★ Insight ──────────────────────────────────────────────────────────────────Claude Vision ist kein Image-Parser im klassischen Sinn — es ist ein Struktur-Erkenner. Das verändert, was "Datenverlust" überhaupt bedeutet, wenn man das exportierte Rendering noch hat. Die Quelle zu verlieren und trotzdem das Bild zu haben, war bis vor kurzem ein Einbahn-Problem. Das ist es nicht mehr.
────────────────────────────────────────────────────────────────────────────
Quelle 6: curl_cffi + Chrome-Cookies gegen Cloudflare
Vier Medium-Artikel existierten nur noch publiziert. Die Markdown-Quellen waren weg, die Metadata-Dateien auch. Medium hat keinen Export pro Artikel im Markdown-Format angeboten, als ich nach einem suchte — und selbst wenn: Medium ist Cloudflare-geschützt, und wer automatisiert an Inhalte will, bekommt regelmäßig Turnstile-Challenges.
Die Lösung — und hier wird der Ironie-Pegel hoch — ist eine Kombination aus curl_cffi (impersoniert den TLS-Fingerprint eines echten Chrome 120) und browser_cookie3 (liest meine Medium-Session-Cookies aus Chrome Profile 1). Das Ergebnis ist eine HTTP-Anfrage, die Cloudflare nicht von einem echten Browser unterscheiden kann — weil sie, aus TLS-Sicht, auch genau das ist.
from curl_cffi import requests
import browser_cookie3
import os
cookie_file = os.path.expanduser(
"~/Library/Application Support/Google/Chrome/Profile 1/Cookies"
)
cj = browser_cookie3.chrome(domain_name="medium.com", cookie_file=cookie_file)
session = requests.Session()
for c in cj:
session.cookies.set(c.name, c.value, domain=c.domain)
r = session.get(url, impersonate="chrome120")
r.raise_for_status()
print(r.text)
Damit das klar ist: Das ist nicht "Medium umgehen" — ich scrape meine eigenen Artikel. Ich habe das Recht auf diesen Inhalt, nur nicht mehr den Zugriff auf die Quelldateien. Vier Artikel kamen als Markdown zurück. Nebenbemerkung, die zum Artikel passt: Ich scrape meine eigenen Medium-Artikel, um diesen Artikel über die Recovery schreiben zu können — den ich danach wieder auf Medium publizieren werde.
Was nicht zurückkam
Ehrliche Inventur statt heroischer Endsumme: 78 Dateien blieben fehlend. Grob kategorisiert:
| Kategorie | Grund |
|---|---|
| Feature-Image-Zwischenversionen (JPEG, PNG) | Binärdateien tauchen nicht in JSONL auf |
| Alte Draft-Versionen ohne Write-Event | Nur in der Pack-Datei, dort verloren |
| Referenzmaterial (Screenshots, PDFs) | Binär, nicht in JSONL |
Ein Excalidraw-Set (sdd1-speckit-workflow) | Kein PNG vorhanden, also auch nichts für Vision |
Temporäre Notizen und .tmp-Dateien | Nie in einem Write/Edit-Tool |
In den Tagen nach dem Samstag kamen noch rund 52 Dateien nachträglich hinzu — Worktree-JSONLs, die ich beim ersten Durchlauf übersehen hatte, zusätzliche Publii-Media-Assets, vereinzelte Excalidraw-Rekonstruktionen. Der finale Stand nach abgeschlossener Recovery-Arbeit: 412 von 490 Dateien, rund 84 Prozent, nachweisbar über git ls-files | wc -l im Initial-Commit f3d9c09 ("Initial commit: Recovered repository"). Von den fehlenden 78 sind die meisten entweder über Plattformen rekonstruierbar oder waren Referenzmaterial, nicht Quelldaten.
Die eigentliche Lektion
Claude Codes JSONL-Logs waren kein Backup. Sie waren ein Nebenprodukt, das wie ein Backup funktioniert hat. Das ist ein feiner, aber wichtiger Unterschied. Ein Backup ist eine bewusste Entscheidung. Ein Nebenprodukt ist Glück — und Glück ist keine Strategie.
Die 84-Prozent-Recovery war kein Beweis dafür, dass Claude Code Backups ersetzt. Sie war Beweis dafür, dass ich lange sehr, sehr viel Glück hatte. Dass die JSONL-Logs überhaupt noch da waren, ging auf eine Frustration über einen Tool-Wechsel zurück, nicht auf irgendeine Backup-Planung. Dass die Publii-DB außerhalb von ~/projects/private/ lag, war ein historischer Zufall. Dass die PNGs in Publii-Media statt im Repo lagen, ebenso. Jede einzelne dieser Quellen hätte fehlen können. Dann wären es nicht 84 Prozent, sondern 40 oder 20 gewesen.
Jeder Entwickler kennt die Regel. Wenige befolgen sie. Ich gehöre jetzt zu den wenigen — nicht weil ich es wollte, sondern weil es keine Alternative mehr gab. Die Einsicht, die sich über zwölf Recovery-Tage eingeschlichen hat, ist unspektakulär: Backup ist keine Intelligenzfrage. Ich habe nicht "jetzt verstanden, dass Backup wichtig ist" — das wusste ich vorher. Ich habe jetzt einfach keinen Weg mehr, mir einzureden, dass es mich nicht betrifft. Das ist ein Unterschied in der Ehrlichkeit gegenüber sich selbst, nicht im Wissensstand.
Was jetzt anders ist
TimeMachine läuft seit dem Vorfall. Stündliche Snapshots über SMB auf einen dedizierten Share, nicht die vage "Zeitmaschine"-Metapher, die mir der Titel erlaubt. cleanupPeriodDays in ~/.claude/settings.json bleibt erhöht — aus klar dokumentiertem Grund, nicht mehr nur aus Roo-Code-Frust. Das blogs-Repo hat jetzt ein Remote auf einem selbstgehosteten Gitea. Nach jedem Commit ein Push-Hook, keine lokalen Inseln mehr.
Am selben Abend des 4. April, während die Recovery-Session noch lief und einen stabilen Stand erreicht hatte, kamen die drei rm -Rf-Befehle, die ich eigentlich um 01:38 Uhr ausführen wollte — diesmal gezielt: calculator um 20:24, tui-calculator um 23:16:24, calc-tui um 23:16:40. Die Zsh-History zeigt alle drei. Einundzwanzig Stunden Umweg für drei Aufräum-Kommandos.
Drei Dinge zum Mitnehmen
Keine 10-Punkte-Liste, keine Checkliste. Drei Dinge:
Wenn du Claude Code produktiv nutzt: Erhöhe
cleanupPeriodDaysin~/.claude/settings.json. Ich habe das damals aus Frustration getan, nicht aus Weitsicht. Rückblickend eine der besten Entscheidungen, die ich getroffen habe, ohne zu wissen, dass ich sie traf. JSONL-Logs sind ein unbeabsichtigter Audit-Trail — du weißt nie, wann du zurückschauen willst, und der Default von 30 Tagen ist dafür zu kurz.Wenn du dich auf
.gitals Backup verlässt: Das ist kein Backup. Git schützt Versionen, nicht Hardware. Einrm -rfräumt beides. Die Pack-Datei liegt im selben Verzeichnis wie der Working Tree, und genau da zielt ein Katastrophen-rmhin.Wenn du Backup-Predigten hältst: Überprüfe dein eigenes Setup. Heute. Nicht morgen. Dieser Artikel wurde nur möglich, weil ich Glück mit meinen eigenen früheren Entscheidungen hatte — nicht weil ich klug war.
Die Zeitmaschine existiert. Aber sie ist nicht das, worauf du bauen solltest.

