Projekt

Apple Wallet Support Tracker: ein Datensatz, der sich selbst pflegt

Ich hatte keine Lust, selbst Buch darüber zu führen, welche Marken Apple Wallet unterstützen. Also habe ich einen offenen Datensatz gebaut, der sich selbst recherchiert und korrigiert: ein monatlicher Cron-Job, ein Claude-Orchestrator und ein Schwarm paralleler Recherche-Agenten.

11 Min. Lesezeit03.06.2026Justin LanfermannQuellcode
An autonomous research agent verifying Apple Wallet support across many brands and opening pull requests

Beim Bauen von NeatPass stieß ich immer wieder auf dieselbe lästige Frage: Stellt mir diese Airline, dieser Verkehrsbetrieb oder dieses Bonusprogramm wirklich einen nativen Apple-Wallet-Pass aus? Eine verlässliche Liste dazu gibt es nicht. Die Antwort ändert sich ständig, sie steckt verstreut in Support-Seiten und Forenthreads, und jede Tabelle, die man selbst führt, ist nach einem Monat veraltet.

Also habe ich den Apple Wallet Support Tracker gebaut: einen offenen Datensatz darüber, welche Marken Apple Wallet nativ über .pkpass unterstützen. Das Interessante sind nicht die Daten, sondern die Maschinerie dahinter. Ich wollte ihn nicht von Hand pflegen, also pflegt er sich selbst. Ein geplanter Job startet KI-Recherche-Agenten, die jede Marke prüfen, ihre Quellen angeben und mir einen Pull Request zur Durchsicht öffnen. Er ist der Datensatz hinter dem live laufenden NeatPass Wallet Support Tracker, und in diesem Beitrag geht es darum, wie der autonome Teil tatsächlich funktioniert.

Der Datensatz

Jede Marke ist ein Ordner auf der Platte: eine data.json mit der strukturierten Zeile und eine research.md, die festhält, welche Seiten geprüft wurden, samt chronologischer Historie jeder Änderung. Eine generierte index.json ist der schnelle Zugriff für alle, die die Daten nutzen. Pro Marke hält eine Zeile fest:

  • Nativen pkpass-Support, als full, partial oder none.
  • Ob die iOS-App eine Live Activity bietet und ob Pässe auf die Apple Watch synchronisiert werden.
  • Bekannte Probleme aus der Praxis.
  • Eine Liste zitierter Quellen, jede nach Typ markiert: official, support, press oder community.

Dieses letzte Feld ist der ganze Trick. Jeder Fakt muss durch eine URL belegt sein, und jede URL hat eine Priorität. Sobald jeder Fakt seinen Beleg zitieren muss, kann eine Maschine die Daten aktualisieren, ohne dass der Datensatz still und leise zur Fiktion wird.

Die Recherche einem Cron-Job überlassen

Die Kernschleife ist ein GitHub-Actions-Workflow, der am Ersten jedes Monats um 04:00 UTC läuft. Er checkt das Repo aus und übergibt dann einen Prompt an Claude Code, das als Agent läuft. Kein Mensch ist beteiligt, bis ein PR auftaucht.

Der Agent, den er startet, geht nicht stur 54 Marken nacheinander durch. Er ist ein Orchestrator. Seine einzige Aufgabe ist, den Marken-Index zu lesen, ihn in etwa sechs Batches aufzuteilen und pro Batch über das Tool Task einen Subagenten zu starten, alles in einer einzigen Nachricht, damit sie parallel laufen. Dann wartet er, fasst die Ergebnisse zusammen, generiert den Index neu und validiert.

Warum der Aufwand mit dem Fan-out? 54 Marken nacheinander zu prüfen dauert 30 bis 45 Minuten an Websuchen. In parallelen Batches sinkt das auf unter zehn. Jeder Subagent bekommt sein eigenes, fokussiertes Kontextfenster, sodass die Fakten einer Marke nie in eine andere überschwappen. Dem Orchestrator ist eigene Recherche ausdrücklich verboten: prüfe Marken niemals selbst, delegiere immer an Subagenten.

Die deterministische Hülle um einen nicht-deterministischen Agenten

Der Agent ist der schlaue, unvorhersehbare Teil. Alles drumherum ist bewusst simpel. Der Workflow ist eine feste Abfolge von Shell-Schritten, und der Agent ist genau einer davon. Bevor er läuft, ermittelt der Job den aktuellen Monat, findet oder erstellt einen passenden Milestone und schreibt den Umfang des Sweeps in .agent/sweep-config.json. Über diese Datei spricht der Workflow mit dem Agenten: Umfang und ein Dry-Run-Flag werden als Daten übergeben, nicht in den Prompt gestopft. Dann prüft er, ob das benötigte Auth-Token gesetzt ist, lädt prompts/sweep.md von der Festplatte in eine Umgebungsvariable und übergibt erst dann die Kontrolle an Claude.

Der Agenten-Schritt selbst läuft mit continue-on-error, sodass ein wackeliger Modelllauf den ganzen Job nie zum Absturz bringt. Danach führt der Workflow npm run validate noch einmal selbst aus, liest das Ergebnis und verzweigt: Erfolg öffnet einen PR, ein Fehlschlag öffnet ein Issue mit dem Label type:bug, das dem Milestone des Monats zugeordnet wird, damit ein schlechter Sweep nicht einfach verschwinden kann.

Genau diese Trennung ist das ganze Design. Der Agent öffnet nie seinen eigenen PR, bearbeitet nie den Index und darf nie selbst entscheiden, ob seine Arbeit gültig ist. Er recherchiert und bearbeitet Dateien; der Workflow übernimmt die Buchführung, die Validierung und das Git. Verliert das Modell den Faden, ist der schlimmste Fall ein wirkungsloser Lauf und ein automatisch erstellter Bug, kein beschädigter Datensatz auf main.

Agenten beibringen, gute Rechercheure zu sein

Ein Agent, den man auf das offene Web loslässt, zitiert bereitwillig irgendeinen Reddit-Kommentar, als wäre er das Evangelium. Die Lösung ist kein klügeres Modell, sondern ein strengerer Prompt. Jeder Subagent folgt einer kurzen, klaren Prozedur: die bestehende Zeile lesen, höchstens dreimal suchen und bei der ersten vertrauenswürdigen Quelle stoppen, die den Eintrag bestätigt oder widerlegt. Vertrauenswürdigkeit ist gestuft, und die Stufung steht im Prompt:

Community-Quellen dürfen immer nur stützende Belege sein, nie die alleinige Grundlage eines Fakts. Der Agent setzt lastChecked hoch, hängt ein Zitat an, deckelt die Quellenliste bei fünf und schreibt eine einzeilige Notiz in research.md. Außerdem soll er reindex niemals selbst ausführen, denn das macht der Orchestrator genau einmal, nachdem alle Batches fertig sind. Eine kleine Regel, aber genau sie verhindert, dass sich sechs Agenten um dieselbe generierte Datei streiten.

Die Kostendisziplin ist genauso bewusst gewählt. Eine Marke, die in den letzten 30 Tagen geprüft wurde, bekommt nur eine schnelle Auffrischung, keine vollständige Neuuntersuchung. Der ganze Lauf ist auf ein Budget von 120 Turns über Orchestrator und alle Subagenten gedeckelt, bei mittlerer Effort-Stufe. Ein monatlicher Durchlauf kostet am Ende ein paar Dollar, und das ist der Unterschied zwischen einer Automatisierung, die ich wirklich laufen lasse, und einer, die ich nach der ersten Rechnung wieder abschalte.

Zwei Backends, kein einziger API-Schlüssel

Das Verzeichnis prompts/ ist die gesamte Agenten-Definition: drei Markdown-Dateien. sweep.md ist der Orchestrator, sweep-batch.md der Subagent, den er startet, und issue-fix.md der reaktive Handler. Das Gehirn hinter diesen Prompts tauscht man per Dropdown aus. Beide Workflows nehmen einen Eingabewert agent entgegen, claude oder codex, und der Cron nutzt immer Claude. Claude läuft über anthropics/claude-code-action, Codex über codex exec --full-auto auf gpt-5.5. Dieselbe Prompt-Datei, ein anderes Modell.

Keines davon rechnet gegen einen verbrauchsbasierten API-Schlüssel ab. Sie authentifizieren sich mit Abo-Tokens, CLAUDE_CODE_OAUTH_TOKEN oder CODEX_AUTH_JSON, und der Workflow bricht mit einer klaren Fehlermeldung ab, wenn das Secret fehlt, statt klammheimlich auf einen kostenpflichtigen Schlüssel zurückzufallen. Eine außer Kontrolle geratene Schleife kann ihr Turn-Budget verbrennen, aber keine API-Rechnung in die Höhe treiben.

Die Tool-Rechte sind pro Rolle zugeschnitten. Der Sweep-Orchestrator bekommt das Tool Task, um Subagenten zu starten. Der Issue-Handler bewusst nicht, denn ein Issue soll genau eine Marke betreffen und niemals auffächern. Beide bekommen WebSearch, WebFetch, Read, Edit und Bash, sonst nichts. Der Agent hält immer nur die Tools, die seine Aufgabe braucht.

Das Internet ist feindselig, also sind es die Issues auch

Der monatliche Sweep hält den Datensatz frisch, aber die schnellsten Korrekturen kommen von Menschen, die einen Fehler entdecken. Also gibt es einen zweiten Workflow: Man eröffnet ein Issue mit dem Label type:correction oder type:new-brand, und ein Agent nimmt es auf, prüft die Behauptung und öffnet einen PR. Der Haken: Ein Issue-Text ist die nicht vertrauenswürdige Eingabe eines Fremden, und diese Eingabe landet direkt in einem Prompt. Ein Lehrbuchfall für Prompt Injection. Der Workflow packt das Issue in <untrusted-input>-Tags, und der Prompt zieht eine harte Grenze darum:

Der Rest des Handlers ist auf sicheres Scheitern ausgelegt. Gibt die zitierte URL einen 4xx zurück, hat eine Community-Quelle keinen höherwertigen Beleg, oder ist die Anfrage mehrdeutig, ändert der Agent nichts, schreibt stattdessen eine Zusammenfassung, und der Workflow markiert das Issue als needs-human. Ein Idempotenz-Schutz überspringt den Lauf ganz, wenn für dieses Issue schon ein Branch existiert, und Korrekturen von externen Beitragenden brauchen eine manuelle Freigabe, bevor der Agent überhaupt startet. Ein Issue betrifft genau eine Marke.

Einem Roboter meine Daten anvertrauen

Was das Ganze im Betrieb angenehm macht: Die KI ist der Rechercheur, nicht der Committer. Sie schreibt nie direkt in den veröffentlichten Datensatz. Jeder Weg, ob Sweep oder Korrektur, endet bei einem Pull Request, den ich prüfe. Die Leitplanken drumherum sind bewusst langweilig:

  • PRs, niemals Pushes. Der Agent arbeitet auf stage und öffnet einen PR. Releases sind ein separater, manueller Fast-Forward nach main.
  • Validierung sichert jede Änderung ab. Eine Schema-Prüfung, Slug- und Index-Konsistenz, Eindeutigkeit der Marken und Live-Erreichbarkeitstests für URLs müssen alle in der CI bestehen.
  • Die Prompts sind geschützt. Prompt-Dateien, Schema und Workflows stehen unter CODEOWNERS, und dem Agenten ist ausdrücklich verboten, sie zu bearbeiten. Was automatisiert wird, kann seine eigenen Anweisungen nicht umschreiben.
  • Begrenzter Schadensradius. Turn-Budgets, Nebenläufigkeitsgrenzen und die Regel „ein Issue, eine Marke" sorgen dafür, dass ein verwirrter Lauf günstig und eingegrenzt bleibt statt katastrophal.

Nichts davon ist clever. Es ist derselbe Instinkt wie beim PR eines Junior-Beitragenden: freundlich misstrauisch bleiben, die Vorarbeit machen lassen, aber das Diff lesen, bevor es live geht.

Dieselben Schranken gelten für alles, nicht nur für die PRs des Agenten. Ein Test-Workflow führt bei jedem Push und Pull Request npm run validate aus. Eine Konventionsprüfung weist jeden PR ab, der nicht genau ein Label type:* und mindestens ein Label area:* hat, und ein Labeler vergibt die Area-Tags automatisch anhand der geänderten Pfade. Releases sind ein manueller, auf Semver geprüfter Fast-Forward von stage nach main, abgesichert durch ein Branch-Ruleset, das selbst der Release-Job nur mit einem eng begrenzten Token umgehen kann. Ob Agent oder Mensch, der Weg nach main ist identisch.

Fremde davon abhalten, mein Guthaben zu verbrennen

Hier ist eine Lücke, die ich früh bemerkt habe, eine der ersten, die ich geschlossen habe. Der Issue-Handler wird bei gelabelten Issues ausgelöst, und die Issue-Vorlagen vergeben type:correction und type:new-brand automatisch an den, der sie einreicht. Beides zusammen heißt: Jeder im Internet kann ein Issue öffnen, zusehen, wie es automatisch gelabelt wird, und damit einen Agentenlauf starten, der mein Claude- oder Codex-Abo verbraucht. Ein paar Hundert davon sind ein billiger Denial-of-Wallet-Angriff.

Die Lösung ist ein GitHub-Environment, das als Freigabe-Gate dient und pro Lauf dynamisch gewählt wird. Ist der Auslöser der monatliche Cron, oder bin ich es selbst, läuft der Job in agent-auto, ganz ohne Schutzregeln. Alle anderen landen in agent-approval-required, das mich als erforderlichen Reviewer vorsieht. Dort hält der Job an, bevor irgendein Modell startet, sodass das Issue eines Fremden in der Warteschlange auf einen Freigabe-Klick wartet, statt still Tokens zu verbrennen.

Es gibt eine zweite, leisere Kostensicherung. Bevor der Handler überhaupt etwas tut, prüft er am Remote, ob bereits ein Branch agent/issue-<nummer>-<agent> existiert. Tut er das, ist ein früherer Lauf entweder noch unterwegs oder wartet auf Review, also wird übersprungen. Ein erneutes Labeln desselben Issues oder ein doppelter Trigger von GitHub kann sich so nicht in zwei Agentenläufe verzweigen, die jeweils echtes Geld kosten.

Keiner der beiden Tricks ist glamourös. Aber sobald du ein kostenpflichtiges Modell an einen öffentlichen Auslöser hängst, ist Missbrauch nicht mehr hypothetisch. Die günstigste Versicherung ist, dafür zu sorgen, dass nichts Teures je läuft, ohne dass ein Zeitplan oder ein Mensch dahintersteht.

Die Schleife schließen

Ein Datensatz bringt nur etwas, wenn ihn auch etwas nutzt. Der NeatPass Wallet Support Tracker zieht das rohe JSON des Repos zur Build-Zeit, validiert es und generiert daraus ein typisiertes Modul, das die Seite als filterbare Tabelle rendert. Weil das Ganze an ein Release-Tag gepinnt ist, kann ein schlechter Tag im Upstream nie ein Deployment kaputt machen.

Und die Schleife schließt sich von selbst. Jede Zeile auf dieser Seite hat einen Korrektur melden-Link, der ein vorausgefülltes Issue im Repo öffnet, das der Issue-Handler-Agent dann aufnimmt, prüft und in einen PR verwandelt. Ein Leser, der einen veralteten Fakt entdeckt, wird zum Auslöser der nächsten autonomen Korrektur. Das System wird jedes Mal ein kleines Stück korrekter, wenn es jemand benutzt.

Was ich nicht als perfekt verkaufen würde

Das ist Automatisierung, der ich vertraue, keine Zauberei. Ein paar ehrliche Einschränkungen:

  • Ein LLM kann immer noch eine selbstbewusste, gut geschriebene Quelle zitieren, die zufällig falsch ist. Die Regeln zur Quellenpriorität verringern das, sie beseitigen es nicht. Der menschliche Prüfschritt ist tragend, nicht zeremoniell.
  • Der Monatsrhythmus ist ein Kompromiss zwischen Aktualität und Kosten. Eine Marke, die sich am 2. ändert, wartet 29 Tage auf den Sweep, sofern nicht vorher jemand ein Issue eröffnet.
  • Fakten über Apple Wallet veralten schnell. Der Disclaimer des Datensatzes sagt es unverblümt: vor dem Handeln anhand der zitierten Quellen gegenprüfen. Genau darum zeigt der Tracker an, wann etwas zuletzt geprüft wurde.

Ressourcen

Den vollständigen Quellcode, den Datensatz, die Prompts und die Workflows findest du auf GitHub, und die Daten in Aktion im NeatPass Wallet Support Tracker. Wenn dir eine falsche oder fehlende Marke auffällt, ist der Korrektur-Link auf dieser Seite der schnellste Weg, sie wieder in die Schleife einzuspeisen.