[{"data":1,"prerenderedAt":504},["ShallowReactive",2],{"blog-de-blocking-war-der-bug":3,"header-blog-translations-/de/blog/blocking-war-der-bug":501},{"id":4,"title":5,"author":6,"body":7,"date":483,"description":484,"draft":485,"extension":486,"image":487,"meta":488,"navigation":489,"path":490,"seo":491,"stem":492,"tags":493,"translationKey":499,"__hash__":500},"blog_de/blog/de/blocking-war-der-bug.md","Blocking war der Bug","Patrick Hofmann",{"type":8,"value":9,"toc":474},"minimark",[10,14,22,40,46,49,54,153,156,160,163,185,191,201,216,219,223,230,241,256,259,288,291,321,324,328,335,373,383,403,409,412,416,419,426,432,435,445,448,451,453],[11,12,13],"p",{},"Am Samstag dachte ich, die Shell wäre fertig.",[11,15,16,17,21],{},"Ich hatte das Wochenende damit verbracht, ",[18,19,20],"code",{},"ape-shell"," von einem argv-rewriting Wrapper zu einer echten interaktiven Shell umzubauen: persistente bash über einen pty-bridge, marker-basierte Prompt-Detection, Grant-Integration direkt im REPL, Audit-Logging pro Session, Install als Login-Shell. Sieben Milestones, zehn Pull Requests. Die Test-Suite war grün, ich hatte sie als mein Login-Shell produktiv in Benutzung, und ich war überzeugt, das Ding ist reif.",[11,23,24,25,27,28,31,32,35,36],{},"Am Sonntag ist mir noch eine Sache aufgefallen, die an der Architektur knirschte. Wenn ein AI-Agent auf der anderen Seite der Telegram-Leitung einen Befehl abschickt, der einen Grant braucht, blockiert die Shell in einem Polling-Loop auf Genehmigung. Der User sieht davon in Telegram nichts, weil niemand ihn informiert, dass gewartet wird. Ich habe deswegen eine Notification-Komponente gebaut: wenn ",[18,26,20],{}," in den Wait-State geht, ruft sie einen konfigurierten Shell-Command auf, und der darf tun was er will — Telegram-Bot, macOS-Notification, ",[18,29,30],{},"say",", ",[18,33,34],{},"ntfy",", alles. Fire-and-forget, detached, zehn Sekunden Kill-Timeout. Sechs Unit-Tests plus ein E2E-Test, am Sonntagabend committed. Ich dachte: ",[37,38,39],"em",{},"jetzt weiß der User auch, wann er approven soll.",[11,41,42,43,45],{},"Am Montag habe ich das Ganze zum ersten Mal End-to-End benutzt: openclaw auf meinem Rechner zuhause, ",[18,44,20],{}," als Login-Shell des openclaw-Benutzers, Commands über Telegram, ich selbst am Tisch mit dem Handy in der Hand, zwei Meter vom Server entfernt. Das Setup sollte in der einfachsten Konfiguration funktionieren, bevor ich irgendein schwierigeres testen würde.",[11,47,48],{},"Was ich dabei gefunden habe, war eine Reihe sehr verschiedener Probleme. Eines davon war fundamental genug, dass ich am Ende nicht einen Bug gefixt, sondern eine Design-Entscheidung rückwärts durchdacht und das halbe Modell neu geschnitten habe.",[50,51,53],"h2",{"id":52},"was-beim-ersten-test-aufgefallen-ist","Was beim ersten Test aufgefallen ist",[55,56,57,72,82,100,120,134,147],"ul",{},[58,59,60,64,65,68,69,71],"li",{},[61,62,63],"strong",{},"Mein Agent benutzt die Shell für einfache File-Operationen gar nicht erst."," openclaw hat eingebaute Tools für Lesen, Schreiben, Editieren — die laufen direkt durch, ohne je in ",[18,66,67],{},"exec"," zu gehen. ",[18,70,20],{}," schützt eine Schicht, die der Agent für viele Aufgaben ohnehin nicht mehr betritt. Das ist kein Bug, das ist eine Kategorie-Verwechslung in meinem eigenen Security-Modell.",[58,73,74,77,78,81],{},[61,75,76],{},"Cache-Hits auf bereits approved Grants laufen stumm."," Wenn der Agent einen Command ausführt, für den die laufende Session schon eine Zustimmung hat, ist das Verhalten von außen ununterscheidbar von ",[37,79,80],{},"\"da gibt es gar keine Shell\"",": kein Ack, kein Log, nichts.",[58,83,84,87,88,91,92,95,96,99],{},[61,85,86],{},"Der Approve-Flow selbst war unsichtbar."," Man sah ",[37,89,90],{},"\"Requesting grant for: ...\"",", dann ",[37,93,94],{},"\"Approve at: ...\"",", klickte im Browser, und dann stand da einfach der Command-Output. Die eine Zeile ",[37,97,98],{},"\"Grant approved, continuing\""," fehlte, die den State-Flip sichtbar macht.",[58,101,102,108,109,111,112,115,116,119],{},[61,103,104,107],{},[18,105,106],{},"apes grants list"," aus der REPL heraus brach."," Tippt man innerhalb einer interaktiven ",[18,110,20],{},"-Session ein ",[18,113,114],{},"apes \u003Csubcommand>",", kommt ",[18,117,118],{},"ape-shell: unsupported invocation",". Self-Inspection unmöglich. Der Grund hat sich später als sehr unangenehm herausgestellt, dazu gleich.",[58,121,122,125,126,129,130,133],{},[61,123,124],{},"Der Silent-Agent-Block."," Das Kern-Symptom: Agent sagt in Telegram ",[37,127,128],{},"\"bitte den Grant approven\"",", ich approve im Browser, dann passiert nichts. Nach einer Weile muss ich im Chat ",[37,131,132],{},"\"bestätigt\""," tippen, damit der Agent weiterläuft. Der Loop schließt sich nicht von selbst.",[58,135,136,139,140,142,143,146],{},[61,137,138],{},"Die REPL kann in einen nicht-recoverbaren Zustand geraten",", ohne dass es einen Weg von innen gibt, herauszufinden was kaputt ist oder etwas zu reparieren. Verschärft durch das gebrochene ",[18,141,106],{},", weil damit auch ",[18,144,145],{},"apes whoami"," als letzte Notbremse weg war.",[58,148,149,152],{},[61,150,151],{},"Das Diagnose-Paradox."," Alle meine Tools, um die Shell zu inspizieren, liegen innerhalb der Shell. Wenn die Shell kaputt ist, ist auch die Diagnose kaputt.",[11,154,155],{},"Die meisten davon sind sichtbare UX- und Observability-Löcher. Reparierbar. Einer — der Silent-Agent-Block — ist etwas anderes gewesen.",[50,157,159],{"id":158},"meine-hypothesen-waren-alle-falsch-aus-demselben-grund","Meine Hypothesen waren alle falsch, aus demselben Grund",[11,161,162],{},"Ich bin an dem Silent-Agent-Block hängengeblieben, weil er sich wie der schwerste anfühlte. Ich habe mir mehrere Hypothesen aufgeschrieben, jede mit einem Reproduktions-Test und einem daraus abgeleiteten Fix.",[11,164,165,166,168,169,172,173,176,177,180,181,184],{},"Die eine: das LLM polled nicht. Wenn ",[18,167,67],{}," in openclaw nach rund einer halben Minute mit ",[37,170,171],{},"\"Command still running, session X, use process tool for follow-up\""," an den Agent zurückkehrt, sollte das LLM als nächsten Schritt einen ",[18,174,175],{},"process(action=poll, sessionId=X, timeout=...)"," absetzen. In meinem Fall tat es das nicht — es hat die Nachricht ",[37,178,179],{},"\"bitte approve\""," an Telegram weitergegeben und seinen Turn beendet. Wenn dann der Grant kam und der Prozess im Hintergrund terminierte, rief openclaw zwar korrekt ",[18,182,183],{},"requestHeartbeatNow(\"exec-event\")"," auf, aber der Agent wachte trotzdem nicht weiter auf, weil er nichts Neues im User-Message-Queue fand. Fix: stärkerer Hint im Yield-Result, der das LLM zwingt, den Poll zu schedulen.",[11,186,187,188,190],{},"Die andere: der Heartbeat-Wake traf die falsche Session. Vielleicht ist der Session-Key des gebackgrounded ",[18,189,67],{},"-Runs nicht identisch mit dem Session-Key der Telegram-gebundenen Agent-Session — dann zielt die Wake ins Leere. Fix: Session-Key-Mapping reparieren.",[11,192,193,194,196,197,200],{},"Und eine dritte: ",[18,195,20],{}," terminiert nach dem Approve nicht sauber. Vielleicht lief der Grant-Wait-Loop bis zum Approve durch, aber der Shell-Child blieb danach in einem Zustand hängen, den openclaw nicht als ",[37,198,199],{},"\"exit\""," sehen kann. Fix: Exit-Semantik im Grant-Dispatcher korrigieren.",[11,202,203,204,207,208,211,212,215],{},"Ich habe den Plan geschrieben, ihn angeschaut, und dann gemerkt, dass die Hypothesen alle dasselbe tun. Sie fragen, ",[61,205,206],{},"wie"," ich den wartenden Prozess dazu bekomme, den Agent korrekt aufzuwecken. Keine von ihnen fragt, ",[61,209,210],{},"warum"," überhaupt gewartet wird. Sie nehmen ",[37,213,214],{},"\"Shell blockiert, bis der Grant approved ist\""," als gegeben und versuchen, das Aufwecken hinterher zu reparieren.",[11,217,218],{},"Das ist die Stelle, an der ich eine Weile in der falschen Ecke gesucht habe, bevor der Groschen fiel. Blocking war der Bug. Nicht der Timeout, nicht der Heartbeat, nicht der Session-Key. Der Wait selbst.",[50,220,222],{"id":221},"warum-warten-das-falsche-primitiv-war","Warum Warten das falsche Primitiv war",[11,224,225,226,229],{},"Mein ursprüngliches Design hat sich wie normal bash angefühlt: du feuerst einen Command, er läuft, er terminiert, du bekommst einen Exit-Code. Der Grant-Flow hat sich als ",[37,227,228],{},"\"Schritt vor dem Ausführen\""," eingefügt, in den die Shell eben synchron hineinwartet. Für einen Menschen am Terminal ist das richtig. Der Mensch sitzt da, klickt die Approve-URL, kommt zurück, sieht den Output. Eine Sekunde, vielleicht fünf. Kein Problem.",[11,231,232,233,236,237,240],{},"Für einen AI-Agent, der über Telegram mit mir kommuniziert und ich als User potentiell an ganz anderer Stelle bin, ist genau das die falsche Semantik. Der Agent hat gerade eine Message abgesetzt, ",[37,234,235],{},"\"bitte approve den Grant\"",". Ich bin irgendwo — im selben Raum, im anderen Zimmer, im Gespräch, in einem anderen Task. Ich approve vielleicht sofort, vielleicht in zwei Minuten, vielleicht heute Abend. In der Zwischenzeit ",[61,238,239],{},"soll der Agent nicht blockieren",". Er soll andere Anfragen bearbeiten können, anderen Usern antworten, parallele Tasks erledigen. Das Warten ist nicht nur kosmetisch unangenehm — es ist architektonisch die falsche Semantik für einen asynchronen Human-in-the-Loop.",[11,242,243,244,247,248,251,252,255],{},"Der richtige Default für einen grant-gesicherten Command ist ",[37,245,246],{},"Fire, bekanntgeben, Exit 0",". ",[18,249,250],{},"apes run"," feuert den Grant-Request, druckt die ID und die Approve-URL auf stdout, feuert die konfigurierte Notification out-of-band, und beendet sich sofort mit Exit-Code 0. Der Agent macht andere Dinge. Ich approve im Browser, wenn ich dazu komme. Später ruft der Agent ",[18,253,254],{},"apes grants run \u003Cid>"," auf und holt sich das tatsächliche Command-Ergebnis.",[11,257,258],{},"Zwei Schritte statt einem, ja — aber zwei Schritte mit einem natürlichen Übergabepunkt, an dem der Agent nicht warten muss und an dem der Mensch nicht pünktlich sein muss.",[11,260,261,262,265,266,268,269,272,273,276,277,280,281,283,284,287],{},"Das ist gestern Abend als ",[18,263,264],{},"@openape/apes@0.9.0"," auf npm gelandet. ",[18,267,250],{}," und ",[18,270,271],{},"ape-shell -c"," sind jetzt non-blocking per Default. Blocking bleibt verfügbar als opt-in: ",[18,274,275],{},"--wait"," auf der Commandline oder ",[18,278,279],{},"APE_WAIT=1"," als Environment-Variable. CI-Skripte, die weiter auf den Exit-Code des tatsächlichen Commands warten wollen, können das mit dem Flag genauso wie vorher tun. Der interaktive REPL (",[18,282,20],{}," ohne ",[18,285,286],{},"-c",") bleibt unberührt, weil da tatsächlich ein Mensch am Prompt sitzt, der warten darf und soll.",[11,289,290],{},"Zusammen mit der Pending-Notification aus der Vortagsversion ergibt das ein neues Muster:",[292,293,294,300,306,309,312,318],"ol",{},[58,295,296,297],{},"Agent feuert ",[18,298,299],{},"apes run -- curl https://example.com",[58,301,302,305],{},[18,303,304],{},"apes"," erzeugt den Grant, druckt Grant-ID, Approve-URL und Ausführ-Hinweis, ruft den Notification-Command auf, Exit 0",[58,307,308],{},"Agent arbeitet weiter",[58,310,311],{},"Ich sehe auf dem Handy die Notification, approve im Browser",[58,313,314,315,317],{},"Wenn der Agent bereit ist, ruft er ",[18,316,254],{}," und bekommt den Output",[58,319,320],{},"Kein Schritt davon blockiert irgendwas",[11,322,323],{},"Das Silent-Agent-Block-Problem ist damit nicht gefixt. Es existiert nicht mehr. Es gibt keinen Block, an dem etwas silent hängen könnte, weil es gar keinen Block gibt.",[50,325,327],{"id":326},"was-sonst-gefixt-wurde-in-reihenfolge-des-lerneffekts","Was sonst gefixt wurde, in Reihenfolge des Lerneffekts",[11,329,330,331,334],{},"Parallel zum 0.9.0-Redesign habe ich die anderen Mängel in einem früher gelandeten Release behoben — ",[18,332,333],{},"@openape/apes@0.8.0",", drei PRs.",[11,336,337,343,344,347,348,350,351,354,355,358,359,361,362,364,365,368,369,372],{},[61,338,339,340,342],{},"Der gebrochene ",[18,341,106],{}," in der REPL."," Der Rootcause war nicht, was ich erwartet hatte. Ich hatte ",[37,345,346],{},"\"Argv-Parsing-Bug oder Dispatch-Regel falsch\""," vermutet und war darauf eingestellt, im REPL-Command-Handler zu debuggen. Der tatsächliche Grund war ein leakender Environment-Marker. ",[18,349,20],{}," setzt intern ",[18,352,353],{},"APES_SHELL_WRAPPER=1",", damit das CLI erkennt ",[37,356,357],{},"\"ich wurde als ape-shell invoked\"",". Dieses Env-Var wurde dann über die pty-bridge an den bash-Child weitervererbt, und von dort an jeden darin aufgerufenen ",[18,360,304],{},"-Subcommand. Der nested ",[18,363,304],{}," sah den Marker und dachte, ",[37,366,367],{},"er"," sei selbst eine ape-shell-Invocation, fand die Subcommand-Args nicht im ape-shell-Argv-Schema, und warf ",[18,370,371],{},"unsupported invocation",". Der Fix ist eine Zeile: beim pty-Spawn den Marker aus dem Environment rausdestrukturieren, bevor es an bash geht. Das Aufwändige war nicht der Fix, sondern dass ich mich auf der falschen Ebene umgeschaut habe — Dispatch-Logik statt Environment-Vererbung.",[11,374,375,378,379,382],{},[61,376,377],{},"Die unsichtbaren Cache-Hits und Approvals"," sind zwei bewusste ",[18,380,381],{},"consola.info","-Zeilen im Grant-Dispatcher. Triviale Fixes — ich hatte sie beim ersten Bauen schlicht vergessen, weil ich nicht über Observability nachgedacht habe, sondern über Funktion. Ein funktionierendes System ist nicht dasselbe wie ein beobachtbares System, und das merkt man fast immer erst in dem Moment, in dem man beobachten will.",[11,384,385,388,389,31,392,31,395,398,399,402],{},[61,386,387],{},"REPL-Recovery und externes Health-Probe"," sind ein paar neue Meta-Commands (",[18,390,391],{},":help",[18,393,394],{},":status",[18,396,397],{},":reset",") in der REPL, plus ein neuer Subcommand ",[18,400,401],{},"apes health",", der standalone aus jeder Shell läuft und die komplette Auth- und Config-State ausgibt. Mit letzterem habe ich das Diagnose-Paradox umgangen. Ich kann jetzt von außen prüfen, ob meine Shell gesund ist, ohne in die potentiell kaputte REPL zu müssen.",[11,404,405,408],{},[61,406,407],{},"Den Shell-Bypass habe ich bewusst nicht gefixt."," Dazu gleich.",[11,410,411],{},"Das alles ist 0.8.0, mit einigen Shipping-Hürden unterwegs, aber am Ende live.",[50,413,415],{"id":414},"was-ich-mitnehme","Was ich mitnehme",[11,417,418],{},"Was mich nicht mehr loslässt, ist der Moment, in dem ich mehrere Hypothesen aufgeschrieben hatte und keine davon stimmte — weil sie alle dieselbe Annahme teilten.",[11,420,421,422,425],{},"Das ist die Art Fehler, die man macht, wenn man ein bestehendes Design als gegeben nimmt und nur die Bugs ",[37,423,424],{},"innerhalb"," davon sucht. Die Hypothesen waren lokal richtig gedacht — jede einzelne hätte, wäre sie zutreffend gewesen, zu einem sauberen Fix geführt. Aber lokal richtig ist nicht genug, wenn die falsche Annahme eine Ebene darüber liegt.",[11,427,428,431],{},[61,429,430],{},"Wenn du beim Debugging mehrere parallele Hypothesen brauchst, die alle dasselbe Default-Verhalten voraussetzen, halte an und frage, ob dieses Default-Verhalten überhaupt richtig ist."," Mehrere gleichzeitige Hypothesen sind ein stärkeres Signal für ein Architektur-Problem als für einen Bug. Ein Bug hat meistens genau eine plausible Ursache. Ein Architektur-Problem hat mehrere — und jede sieht lokal wie ein Bug aus.",[433,434],"hr",{},[11,436,437,440,441,444],{},[61,438,439],{},"Offenes Ende."," Der Agent umgeht meine grant-gesicherte Shell für viele einfache Operationen komplett, weil er eingebaute Tools hat, die direkt auf dem Filesystem arbeiten. Das ist kein Bug. Das ist eine strukturelle Eigenschaft moderner Tool-basierter Agent-Frameworks, und sie verschwindet nicht, indem ich einen anderen Loop fixe. Es stellt die Frage, ",[61,442,443],{},"was eine Grant-gesicherte Shell überhaupt wert ist, wenn der Agent für die meisten Aktionen keine Shell mehr braucht",".",[11,446,447],{},"Auf diese Frage habe ich noch keine gute Antwort. Sie ist nicht durch ein Release lösbar. Sie ist das Thema der nächsten Wochen, und ich bin ehrlich gesagt nicht sicher, ob sie zu einer Feature-Entscheidung oder zu einer Architektur-Entscheidung oder zu einem ganz anderen Produkt führt. Ich mag sie trotzdem lieber als ein Problem, bei dem ich schon weiß, was ich tun werde — weil sie mir etwas beibringt, während ich darüber nachdenke.",[11,449,450],{},"Wenn jemand von euch ein ähnliches Muster beim Debuggen schon einmal getroffen hat — wo ihr gemerkt habt, dass eure Hypothesen alle auf derselben falschen Annahme aufsetzen — schreibt mir gerne. Ich sammle gerade Beispiele, nicht für einen weiteren Post, sondern weil ich das Muster besser verstehen will.",[433,452],{},[11,454,455],{},[37,456,457,459,460,467,468,473],{},[18,458,264],{}," ist seit gestern Abend live auf npm. Der Code liegt auf ",[461,462,466],"a",{"href":463,"rel":464},"https://github.com/openape-ai/openape",[465],"nofollow","github.com/openape-ai/openape",". Die ",[461,469,472],{"href":470,"rel":471},"https://www.delta-mind.at/de/blog",[465],"vorigen beiden Blog-Artikel"," erzählen, wie die Shell überhaupt entstanden ist und wie der pty-bridge funktioniert, der ihr das Leben gibt.",{"title":475,"searchDepth":476,"depth":476,"links":477},"",2,[478,479,480,481,482],{"id":52,"depth":476,"text":53},{"id":158,"depth":476,"text":159},{"id":221,"depth":476,"text":222},{"id":326,"depth":476,"text":327},{"id":414,"depth":476,"text":415},"2026-04-14","Letztes Wochenende dachte ich, meine grant-gesicherte Shell wäre fertig. Am Sonntag habe ich eine Notification für den einzigen verbliebenen Schwachpunkt nachgebaut. Am Montag habe ich beim ersten echten End-to-End-Test eine ganze Reihe von Problemen gefunden — und gemerkt, dass eines davon kein Bug war, sondern eine Design-Entscheidung, die ich rückwärts getroffen hatte. Ein Artikel über Hypothesen, die alle dieselbe falsche Annahme teilten, und über den Moment, in dem man merkt, dass man auf der falschen Ebene debuggt.",false,"md",null,{},true,"/blog/de/blocking-war-der-bug",{"title":5,"description":484},"blog/de/blocking-war-der-bug",[494,495,496,497,498],"OpenApe","Systems Design","Human in the Loop","Building in Public","Debugging","blocking-was-the-bug","tX9JKy4ddZUskZKqrpCnLceGVIIA0S2Xk5OaT0L31CA",{"de":502,"en":503},"/de/blog/blocking-war-der-bug","/en/blog/blocking-was-the-bug",1776970806600]