Zehn Agenten starteten denselben Typecheck gleichzeitig. Die Lösung war ein Verzeichnis.
Siebzehn Coding-Agenten in einem Checkout, zehn tsc-Prozesse gleichzeitig, load average 37 und 87 MB freier RAM. Ein Typecheck von neunzig Sekunden brauchte 7 min 36. Hier ist die Messung, warum die Maschine nicht gerechnet hat, und das kleine geteilte Lock, das es gelöst hat. In jedes Repository kopierbar.
Am 7. September hat unsere Entwicklungsmaschine aufgehört, richtig zu reagieren. Kein Absturz, kein Einfrieren. Alles dauerte einfach zehnmal so lange, auch Dinge, die nichts mit Code zu tun hatten.
Die naheliegenden Verdächtigen waren alle falsch. Der Laptop wurde nicht zu heiß: kein Throttling protokolliert, Akku bei 30,6 C. Kein Amok laufender Prozess fraß die CPU. Nichts war deployt worden. Das Einzige, was ungewöhnlich war: siebzehn Agenten-CLI waren im selben Repository am Leben, was hier ein normaler Arbeitstag ist.
Hier ist, was wirklich passiert ist, gemessen statt geraten.
Die Messung
Eine Maschine mit 16 GB und 8 Kernen, seit fünfeinhalb Stunden an, siebzehn Agenten bei der Arbeit:
| Was wir gemessen haben | Wert |
|---|---|
| Lebende Agenten-CLI | 17 |
Gleichzeitige tsc --noEmit-Prozesse, innerhalb von zwei Minuten gesehen | 3, dann 10 |
| Load average | 37 bis 41 |
| Freier RAM / Speicherkompressor | 87 MB / 7,2 GB |
| Ein Typecheck der Desktop-App, gesättigte Maschine | 7 min 36 Wanduhr für 26 s CPU |
| Derselbe Typecheck, ruhige Maschine | 33 s |
Die entscheidende Zeile ist die vorletzte. Sechsundzwanzig Sekunden CPU, verteilt über siebeneinhalb Minuten, sind zehn Prozent Auslastung. Der Typecheck hat nicht gerechnet. Er hat auf Speicher gewartet.
Und einer dieser Prozesse wurde vom Betriebssystem mitten im Lauf abgeschossen. Ein abgeschossener tsc beendet sich mit einem Code ungleich null und leerer Ausgabe, was von einem echten Typfehler nicht zu unterscheiden ist. Die Maschine war also nicht nur langsam, sie lieferte auch Urteile, denen niemand trauen konnte.
Niemand hat etwas falsch gemacht
Das ist der Teil, bei dem es sich lohnt zu verweilen, denn er macht den Ausfall so schwer vorhersehbar.
Jeder dieser Agenten hat die Regel befolgt. Jeder hatte TypeScript bearbeitet. Jeder hatte die Anweisung, seine Typen zu prüfen, bevor er die Hand zurückgibt. Jeder hat tsc --noEmit gestartet. Keiner hat die anderen gesehen. Es gibt keine gemeinsame Tafel, auf die ein Agent schreiben würde: ich mache gerade die teure Sache, wartet kurz.
Danach nährt es sich selbst. Der Typecheck wird langsamer, weil die Maschine gesättigt ist. Der Agent, der ihn beobachtet, schließt daraus, dass er hängt. Also killt er ihn und startet einen neuen. Dieser Reflex ist für sich genommen richtig und in der Gruppe katastrophal, und es ist dieselbe Ausfallfamilie, die wir einen Monat früher dokumentiert haben, als Agenten hängende Suchprozesse zurückgelassen haben: Process Guard ist das Netz, das findet, was gestartet wurde, hier verhindern wir den Start.
Die drei Antworten, die wir nicht genommen haben
Weniger Agenten starten. Das halbiert das Symptom und behält den Fehler. Zwei gleichzeitige Typechecks auf einer belasteten Maschine sind immer noch langsamer als einer, und die Flotte zu verkleinern heißt, das Problem mit genau dem zu bezahlen, was die Arbeit schnell macht.
Ein einziger Typecheck am Ende. Verlockend, und falsch aus einem Grund, der nichts mit Performance zu tun hat. Ein Typfehler, der zehn Tickets später auffällt, ist verwaist: der Agent, der ihn geschrieben hat, ist geschlossen, sein Kontext ist weg, und ein Mensch muss das ganze Thema wieder aufmachen, um eine Zeile zu korrigieren. Wir wollten die Prüfung nicht verschieben.
Inkrementelle Kompilierung. Getestet und verworfen. Der Gewinn ist im Modus --noEmit zweifelhaft, und gleichzeitige Prozesse beschädigen die geteilte .tsbuildinfo. Sie löst die Hälfte des Problems, indem sie die andere Hälfte verschlimmert.
Was wir stattdessen gemacht haben: eine Prüfung, geteilt
Die Regel lautet nicht "seltener prüfen". Sie lautet ein Typecheck gleichzeitig, pro Projekt, für alle. Ein Wrapper-Skript ersetzt N Prüfungen durch eine einzige und beantwortet drei Fälle:
- Seit dem letzten Lauf hat sich nichts geändert, also gibt es dessen Ergebnis zurück.
- Ein Lauf ist bereits unterwegs, also wartet es darauf und nimmt dessen Ergebnis.
- Sonst nimmt es das Lock und ist der einzige
tscauf der Maschine.
Aus Sicht des Agenten hat sich nichts geändert: er tippt yarn typecheck, er bekommt seine Typfehler. Er wartet auch nie länger als vorher, denn ein Lauf, hinter dem er wartet, ist ein Lauf, der vor seinem eigenen gestartet ist. Die Maschine bezahlt einen statt zehn.
Das ist die ganze Idee. Interessant ist, dass beide Mechanismen, die sie braucht, viel kleiner sind, als man denken würde.
Das Lock ist ein Verzeichnis
Keine Datei, keine Datenbank, kein Daemon. Ein Verzeichnis.
try {
mkdirSync(lockDir); // klappt: das Lock gehört uns
} catch (err) {
if (err.code === 'EEXIST') { /* jemand anderes hält es, warten */ }
}
mkdir legt das Verzeichnis entweder an oder scheitert mit EEXIST, und zwar atomar auf macOS, Windows und Linux, ohne Abhängigkeit und ohne nativen Aufruf. Eine Datei zu schreiben und dann zu prüfen, ob sie existiert, wären zwei Operationen, und zwei Operationen sind genau die Stelle, an der sich ein zweiter Agent dazwischenschiebt.
In das Verzeichnis legen wir eine owner.json mit der pid, dem Hostnamen und der Startzeit. Diese Datei dient der Diagnose und dem Erkennen eines toten Locks. Sie ist nie das, was ausschließt.
Ein totes Lock wird in zwei Fällen automatisch übernommen: der besitzende Prozess ist verschwunden (nur geprüft, wenn der Hostname übereinstimmt, denn eine pid bedeutet von Maschine zu Maschine nichts), oder das Lock ist älter als fünfzehn Minuten.
Eine Falle, die uns einen Bug gekostet hat. Zwischen dem mkdir und dem Schreiben der owner.json gibt es ein Fenster, in dem der Besitzer nicht lesbar ist. Das Lock in diesem Fenster für tot zu erklären heißt, es dem zu stehlen, der es gerade genommen hat, also genau das Rennen, das die Datei verhindern soll. Wenn kein Besitzer lesbar ist, urteilen wir deshalb über das Alter des Verzeichnisses, nicht über die fehlende Datei.
Der Fingerabdruck ist ein Datum und eine Anzahl
Fall 1 muss wissen, ob sich seit dem letzten Lauf etwas geändert hat. Die naheliegende Antwort wäre, die Quelldateien zu hashen. Das tun wir nicht.
Der Fingerabdruck ist <jüngste mtime>:<Anzahl Dateien> über die Wurzeln, die aus dem Feld include der tsconfig abgeleitet werden, plus die tsconfig selbst.
Bei 2.300 Dateien kostet es mehr, jedes Byte zu lesen, als die Prüfung einspart, die man sich damit spart. Das Datum allein sieht keine Löschung. Die Anzahl allein sieht keine Änderung. Zusammen decken sie beides ab. Das in Kauf genommene falsche Negativ sind zwei Änderungen in derselben Millisekunde, die die Anzahl gleich lassen, und der schlimmste Fall ist ein ein paar Sekunden veraltetes Cache-Ergebnis, nie ein stiller Typfehler, denn die blockierende Prüfung bleibt die des Builds.
Eine geschriebene Regel hat nicht gereicht, also kam ein Hook dazu
Die Anweisung stand vom ersten Tag an in AGENTS.md: nie tsc direkt, immer das geteilte Skript. Es hat nicht gereicht, und es lohnt sich, ehrlich zu sagen warum.
Nach einer Änderung seine Typen zu prüfen, ist ein tief verankerter Reflex. Unter Druck tippt ein Agent npx tsc --noEmit, ohne die Anweisungen noch einmal zu lesen. Und es reicht ein einziger Agent, der abweicht, um die Meute wiederherzustellen, die das Lock verhindern soll. Über eine Anweisung lässt sich verhandeln. Über einen Hook nicht.
Wir haben also einen PreToolUse-Hook am Bash-Werkzeug angeschlossen, der ein direktes tsc ablehnt und im Ablehnungstext den richtigen Befehl nennt:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "node \"$CLAUDE_PROJECT_DIR/scripts/hooks/block-direct-tsc.mjs\"" }
]
}
]
}
}
Der Hook liest den Werkzeugaufruf als JSON auf stdin, beendet sich mit 2 und dem Grund auf stderr, um abzulehnen, und mit 0, um durchzulassen. Zwei Details machen den Unterschied zwischen einem nützlichen und einem lästigen Hook.
Er erkennt tsc in Befehlsposition, nicht irgendwo in der Zeichenkette. Die drei Buchstaben überall zu suchen würde auch grep -rn tsc AGENTS.md ablehnen. Das Muster verlangt deshalb tsc am Zeilenanfang oder nach ;, &&, ||, | oder (, eventuell mit einem Paket-Runner und einem Pfad davor. Es lässt auch tsc --version durch: es gibt keinen Grund, eine informative Option abzulehnen.
Er lehnt eine zweite Sache ab, die wir nicht vorhergesehen hatten. Wir haben einen Agenten dabei beobachtet, wie er hinter dem Lock wartete, nach einer Weile beschloss, es müsse tot sein, und das Lock-Verzeichnis löschte, um sich zu entsperren. Das startet einen zweiten schweren Prozess neben dem lebenden, also die perfekte Umgehung von allem, was das Lock schützt. Das Löschen des Lock- oder Cache-Verzeichnisses wird deshalb ebenfalls abgelehnt, mit der Erklärung, dass ein totes Lock von allein übernommen wird.
Diese zweite Ablehnung hätten wir nie im Voraus geschrieben. Sie kommt daraus, zu beobachten, was Agenten wirklich tun, wenn sie blockiert sind, und das ist eine bessere Quelle für Leitplanken, als sich auszudenken, was sie tun könnten.
Wo es aufhört
Der Hook ist Claude Code eigen. Die anderen Agenten-CLI der Flotte sehen nur die geschriebene Regel. Das ist ein bekanntes Loch, und wir stehen dazu: eine Leitplanke, die den Großteil der Flotte abdeckt, ist besser als gar keine, solange es keinen Hook-Standard gibt, den jedes CLI liest.
Das geteilte Skript selbst ist anbieterneutral, denn es ist ein Befehl wie jeder andere. Jedes CLI, das yarn typecheck starten kann, profitiert vom Lock, ob es dazu gezwungen wird oder nicht.
Was man daraus mitnimmt
Der Typecheck war unser lautester Fall, kein Sonderfall. Das Muster gilt für jeden Befehl, der teuer ist, über ein kurzes Fenster idempotent bleibt und von jedem Agenten aus demselben guten Grund gestartet wird: Abhängigkeiten installieren, die komplette Testsuite spielen, für die Produktion bauen, einen Dev-Server auf einem festen Port starten.
Drei Fragen, in dieser Reihenfolge, und du hast den ganzen Entwurf:
- Kann ich ein aktuelles Ergebnis wiederverwenden?
- Kann ich dem bereits laufenden Durchlauf beitreten?
- Sonst: bin ich derjenige, der ihn startet, allein?
Wenn mehrere Agenten sich deine Maschine teilen, ist das, was zu messen sich lohnt, nicht, wie viele laufen. Es ist, wie viele davon denselben Befehl in derselben Minute starten. Diese Zahl ist das, was deine Maschine wirklich spürt, und solange du sie nicht anschaust, wirst du die Hitze beschuldigen.
Wenn du den größeren Überblick willst, wie wir mehrere Agenten auf einem Repository laufen lassen, ohne dass sie sich gegenseitig auf die Füße treten, steht der in Coding Agents parallel ausführen, und das Sicherheitsnetz für die Prozesse, die tatsächlich gestartet sind, ist Process Guard. Das geteilte Skript und der Hook liegen beide im Repository von AgentsRoom, also genau dort, wo diese siebzehn Agenten an jenem Nachmittag gearbeitet haben.
AgentsRoom herunterladen
Führe alle deine KI-Agenten aus, in all deinen Projekten, aus einem einzigen Fenster.
Companion-App: Agenten auch unterwegs im Blick behalten
Nutzen Sie Claude, Codex, Antigravity CLI oder einen anderen KI-Anbieter.
Bugs und Wünsche direkt in dein öffentliches Backlog schicken.
Ein Blick auf AgentsRoom in Aktion.
Weiterlesen
Wie man KI-Coding-Agenten in einem Entwicklerteam skalieren kann
Ein Entwickler mit einem Coding-Agenten ist eine Produktivitätsgeschichte. Fünf Entwickler mit zwanzig Agenten sind ein Koordinationsproblem. Hier ist, was zuerst bricht, wenn ein Team wächst, und das Setup, das funktioniert: engagierte Kontextdateien, klare Dateieigentümerschaft, Überprüfung nach Blast-Radius und Kosten, die man tatsächlich sehen kann.
Zum ArtikelClaude Code hält immer nur einen Login aktiv. So nutzen Sie mehrere Konten parallel.
Ein Praxisleitfaden für den Parallelbetrieb eines Arbeitskontos und eines privaten Kontos auf derselben Maschine: die eine Umgebungsvariable, die entscheidet, welcher Login aktiv ist, warum der Weg über die shell ab dem dritten Terminal scheitert, und wie Sie ein Konto fest an ein Projekt binden.
Zum ArtikelMein KI-Laufcoach ist ein Git-Repository und ein Claude-Agent
Ich beende meinen Lauf, meine Uhr synchronisiert sich, und drei Minuten später steht die Analyse in meinem Repository, die Woche ist angepasst und mein Coach hat einen Kommentar unter der Strava-Aktivität hinterlassen. Keine App gebaut, keinen Server geschrieben, keine Rechnung pro Token: ein Claude-Abo, AgentsRoom und Markdown-Dateien. Hier ist der komplette Aufbau, zum Nachbauen.
Zum Artikel