Asset-Preflight unter Windows: Verschieben aus dem Entpackordner scheitert („could not move tools“), verschluckt die Ursache und hinterlässt einen halben Stack
#197
Befund aus dem ersten Installationslauf aus einem echten Release (v8.0.0, Asset preflight.ps1 ohne --archive) – genau der letzte offene Prüfpunkt von #151. Der erste Lauf ist gescheitert, der zweite nach Leeren des Ordners durchgelaufen: ein sporadischer Fehler, der den Nutzer beim ersten Kontakt mit dem Stack zu Handarbeit zwang.
Stand
Erledigt (geschlossen 2026-10-13). Gebaut, veröffentlicht und durch CI bestätigt: 9.0.0-beta.11, Commit 5c9be38 (2026-10-07). CI auf diesem Commit grün: Lauf 602 (Jobs verify und pwsh – darin PSScriptAnalyzer ohne Fund und die PowerShell- und Preflight-Tests) und Lauf 603. Ausgeliefert mit 9.0.0 und 10.0.0.
Abschlussphase:tools/README.md beschrieb den Asset-Modus noch aus der Zeit vor #153 (Entpacken nach <script folder>/chemenu, vorhandenes Ziel abgewiesen) und kannte weder Wiederholung noch Zurückrollen; nachgezogen in 9.0.0-beta.12, Commit 49f8939, CI grün: Lauf 604, Lauf 605. INSTALL.md gegen instructions/preflight.md erneut gelesen: stimmt überein. docs/ enthält keine Seite, die den Asset-Modus beschreibt.
Die Handprüfung am Windows-Zielsystem ist nach #222 verschoben (Betreiberentscheidung 2026-10-13): Die offenen Prüfungen von Hand nach 10.0.0 sind dort gesammelt, damit sie in einem Termin laufen. Belegt ist der Asset-Weg aus einem echten Release bisher unter Linux (Asset preflight.sh von v10.0.0, 2026-10-13: lädt, prüft, entpackt, löscht sich). Der Nachweis der Behebung selbst sind die Tests mit dem Haken (siehe Kriterien), nicht ein einzelner grüner Lauf.
Was passiert ist (Betreiber, Windows 11, pwsh 7.6.6, GitHub Copilot CLI, 2026-10-07)
Lauf 1. Aufforderung: neue Instanz in C:\Repositories\chemenu (leer, ohne .git), Release über …/releases/latest, setup-instance.md befolgen, vorher preflight.md lesen. Der Agent hat das korrekt getan und pwsh -NoProfile -ExecutionPolicy Bypass -File preflight.ps1 gestartet:
Downloading https://gitea.nehmer.net/torben/chemenu/releases/download/v8.0.0/chemenu-stack-8.0.0.tar.gz
sha256 OK
preflight: could not move tools into C:\Repositories\chemenu - the folder holds part of the stack now; empty it (keep .git) and run this again.
Exit 1. Der Agent hat angehalten, nichts gelöscht und nichts wiederholt – das Verhalten des Agenten war richtig, der Fehler lag im Skript. Danach lag laut Betreiber tools im Zielordner, neben den Ordnern davor; ein .chemenu-unpack.* war nicht mehr da. Ob tools dort vollständig war, ist nicht mehr feststellbar.
Diagnoselauf (Copilot, gleicher Rechner, Defender-Echtzeitschutz an): dreimal derselbe Ablauf wie im Skript – Tarball per tar -xzf in <ziel>\.chemenu-unpack.test\ entpacken, sofort jeden Eintrag per Move-Item nach oben verschieben, jede Ausnahme mit Typ, HResult und Dateizahlen protokollieren. Kein Fehler in drei Läufen (509, 207, 182 ms für alle Einträge; tools jeweils 109 von 109 Dateien).
Lauf 2 (Ordner vom Betreiber bis auf preflight.ps1 geleert, sonst nichts geändert): Exit 0, vollständige Installation, Baum-Preflight mit allen Tools OK und venv angelegt.
Analyse gegen den Baum
Das Asset war bis auf die eingesetzten URLs identisch mit tools/preflight.ps1 auf main (geprüft per diff); der Fehler bestand also unverändert im 9.0.0-Kandidaten bis beta.10.
Der Ablauf, der gescheitert ist, wurde auf Windows vorher nie geprüft. Die Handprüfung in #151 (2026-10-01, 8.0.0-beta.17) lief noch mit D40: Entpacken in einen Nachbarordner, dann eine Umbenennung des ganzen Ordners. #153 (a6d07f9) hat das ersetzt: Ziel ist jetzt der Ordner des Skripts selbst, entpackt wird nach <ziel>\.chemenu-unpack.<pid>\chemenu-stack-<version>\, und danach wird jeder Eintrag einzeln per Move-Item nach oben verschoben (tools/preflight.ps1Invoke-AssetMode; dasselbe in tools/preflight.sh mit mv).
Reihenfolge: Get-ChildItem liefert erst Ordner, dann Dateien. Die Ordner im Tarball sind .claude, .github, .vibe, docs, incoming, instructions, kb, raw, reports, tools, types, work. Die neun Ordner vor tools gingen durch. Move-Item hat bei tools etwas am Ziel hinterlassen und trotzdem eine Ausnahme geworfen. Denkbar ist ein Rückfall auf Kopieren und Löschen, bei dem das Löschen der Quelle an einer Sperre scheitert.
Die Ursache wurde verschluckt. Der catch gab $_.Exception.Message nicht aus. Mit $ErrorActionPreference = 'Stop' landet jede Ausnahme dort – welche es war, lässt sich aus Lauf 1 nicht mehr sagen, und der Diagnoselauf hat den Fehler nicht nachgestellt.
Wahrscheinliche Ursache (Hypothese, nicht belegt, aber gestützt): ein kurzlebiges Datei-Handle im frisch entpackten Baum, typischerweise vom Echtzeitschutz (Defender, auf dem Rechner aktiv) oder einem Indexer. Unter Windows scheitert das Umbenennen eines Ordners, solange irgendeine Datei darin ohne FILE_SHARE_DELETE offen ist (Access denied / Sharing Violation). tools/ ist der Ordner mit allen Skripten (*.ps1, *.sh, *.py, sechs ausführbare Dateien) und wird am gründlichsten gescannt. Gestützt wird das dadurch, dass derselbe Ablauf in vier weiteren Läufen (drei Diagnose, ein echter) fehlerfrei war: Der Tarball und der Ablauf sind in Ordnung, der Fehler hängt am Zeitpunkt. Das ist dasselbe Muster, wegen dem npm (graceful-fs) rename unter Windows bei EPERM/EACCES bis zu 60 s wiederholt. Der Tarball selbst ist unauffällig: keine Symlinks, keine Sonderrechte, längster Pfad 74 Zeichen.
Folge: Der Ordner war halb befüllt und damit für jeden neuen Lauf „nicht leer“. Die Meldung schickte den Nutzer zum Leeren von Hand – ein Exit 1, den ein Agent nach preflight.md richtigerweise nicht selbst auflöst.
Entscheidung
D1 – Nach einem endgültig gescheiterten Verschieben wird zurückgerollt (Betreiber, 2026-10-07)
Das Skript entfernt, was es selbst in diesem Lauf verschoben hat (auch einen Teilrest des gescheiterten Eintrags), und den Entpackordner. Danach liegt im Ordner genau das, was vorher darin lag: .git unberührt, das Skript selbst. Meldung: „nothing was installed“, erneut ausführen genügt. Scheitert auch das Zurückrollen, nennt die Meldung die übrig gebliebenen Einträge und sagt wie bisher, dass der Ordner bis auf .git geleert werden muss.
Warum sicher: Der Ordner war vorher nachweislich leer (bis auf .git und das Skript), und entfernt werden nur Namen, die aus dem Archiv stammen und vorher nicht da waren. .git und das Skript selbst werden nie angefasst, auch wenn das Archiv einen gleichnamigen Eintrag hätte.
Warum: Lauf 2 belegt, dass ein neuer Lauf nach dem Leeren genügt – genau diesen Zustand stellt das Zurückrollen ohne Handarbeit her. Die Zusage „nothing was unpacked/installed“ hält damit für jeden Fehler im Asset-Modus.
Verworfen:
Stehen lassen wie bisher (nur Ursache und Wiederholung): Der Nutzer müsste weiter von Hand leeren, und ein Agent darf das nach preflight.md nicht für ihn tun.
Direkt mit tar --strip-components=1 ins Ziel entpacken: Die Ordner-Umbenennung entfiele, aber ein Fehler beim Entpacken (Platte voll, Pfad zu lang) hinterließe einen halben Baum statt „nothing was unpacked“.
D2 – Wiederholung: Zielrest entfernen und den Eintrag neu aus dem Archiv entpacken (Bau, 2026-10-07)
Der Body ließ offen, wie die Wiederholung die Invariante aus Punkt 2 hält. Entschieden: Jeder Versuch nach dem ersten entfernt zuerst, was vom Eintrag schon am Ziel liegt (nie .git oder das Skript), und entpackt dann genau diesen Eintrag neu aus dem Archiv (tar -xzf <archiv> -C <entpackordner> <top>/<eintrag>), bevor er verschiebt.
Warum nicht nur den Zielrest entfernen: Ein Rückfall auf Kopieren und Löschen, dessen Löschen an einer Sperre scheitert, hinterlässt die Quelle unvollständig (schon gelöschte Dateien fehlen dort) und das Ziel vollständig. Den Zielrest wegzuwerfen und die Quelle zu verschieben ergäbe dann einen Eintrag mit fehlenden Dateien, ohne Fehlermeldung. Warum nicht zusammenführen: müsste Datei für Datei entscheiden, welche Seite gilt (ein abgebrochenes Kopieren hinterlässt eine abgeschnittene Zieldatei) – in sh und pwsh je eigener Code für einen Fall, den das Neu-Entpacken ohne Fallunterscheidung abdeckt. Das Archiv liegt bis zum Ende des Asset-Modus ohnehin vor.
Der Testhaken bildet diesen Fall nach: :partialverschiebt einen Teil des Eintrags (Quelle verliert ihn, Ziel hat ihn), statt ihn nur anzulegen. Ein Bau, der nur den Zielrest entfernt, fällt damit im :partial-Test durch (per Mutation in beiden Skripten geprüft).
Was gebaut wurde
Für beide Skripte (preflight.ps1 und preflight.sh, das unter Git Bash auf Windows denselben Schritt macht), Verhalten gleich:
Die Ursache steht in der Meldung. Scheitert ein Verschieben endgültig, nennt die Zeile den Eintrag und die Fehlermeldung des Systems (pwsh: $_.Exception.Message; sh: die stderr-Zeile von mv).
Vorübergehende Sperren werden ausgesessen. Jedes Verschieben wird bei einem Fehler wiederholt, mit wachsender Pause, insgesamt höchstens ca. 30 s je Eintrag. Erst danach gilt es als gescheitert. Die Zahl steht an einer Stelle je Skript ($MovePauses / MOVE_PAUSES = 1 2 3 4 5 6 9 s, also 8 Versuche). Invariante der Wiederholung: Ein Versuch kann schon einen Teil des Eintrags am Ziel hinterlassen haben (siehe Analyse). Eine Wiederholung darf deshalb nicht blind erneut verschieben – sonst landet der Eintrag als tools\tools oder scheitert an „already exists“. Nach der letzten erfolgreichen Wiederholung entspricht der Eintrag am Ziel genau dem im Archiv, und er existiert genau einmal. Wie: D2.
Zurückrollen nach endgültigem Scheitern (D1). Invariante: Danach enthält der Zielordner genau, was er vor dem Lauf enthielt.
TesthakenCHEMENU_PREFLIGHT_MOVE_FAIL=<eintrag>:<n>[:partial] (Muster wie CHEMENU_PREFLIGHT_PLATFORM): das Verschieben von <eintrag> scheitert die ersten <n> Male (n = always für dauerhaft); mit :partial ist nach jedem gescheiterten Versuch ein Teil des Eintrags schon ans Ziel verschoben. Ist der Haken gesetzt, entfallen die Pausen. Im Kopfkommentar beider Skripte dokumentiert wie die vorhandenen Haken; in conftest.py_WIKITOOL_ENV eingetragen.
instructions/preflight.md (Asset-Absatz) beschreibt den Fall: Exit 1 mit „nothing was installed“ – Meldung weitergeben, dann darf der Nutzer das Asset erneut ausführen; meldet das Skript übrig gebliebene Einträge, entscheidet der Nutzer über das Leeren, nicht der Agent.
Akzeptanzkriterien
Die Testkriterien sind lokal belegt (volle Suite 2568 passed; test_preflight.py + test_preflight_pwsh.py zusätzlich im CI-Image chemenu-ci-pwsh unter dash und pwsh, 182 passed) und durch CI-Lauf 602 bestätigt.
Mit CHEMENU_PREFLIGHT_MOVE_FAIL=tools:2 installiert der Asset-Modus vollständig und endet mit dem Exit-Code des Baum-Preflights (beide Skripte). – test_a_move_that_fails_twice_is_retried_into_a_whole_entry[]
Mit CHEMENU_PREFLIGHT_MOVE_FAIL=tools:2:partial ebenso, und danach gibt es kein tools/tools; die Dateiliste unter tools/ stimmt mit der im Archiv überein (beide Skripte). – derselbe Test, [:partial]
Mit CHEMENU_PREFLIGHT_MOVE_FAIL=tools:always endet er mit Exit 1, und die Fehlerzeile enthält tools und die Systemmeldung des letzten Versuchs (beide Skripte). – test_a_move_that_never_works_is_taken_back (prüft „attempt 8“)
Im selben Fall – auch mit :partial – enthält der Zielordner danach genau das, was er vorher enthielt: ein vorhandenes .git mit unverändertem Inhalt und das Skript selbst, sonst nichts – auch kein .chemenu-unpack.*. Ein zweiter Lauf ohne den Haken installiert vollständig. Mit --into (Skript liegt außerhalb) enthält das Ziel danach nur noch ein vorhandenes .git, bzw. ist leer. Einträge, die vor dem gescheiterten verschoben wurden, werden ebenfalls zurückgenommen. – test_a_move_that_never_works_is_taken_back (4 Varianten), test_an_entry_that_moved_before_the_failure_is_taken_back_too
Die Testsuite wird durch die Wartezeit nicht spürbar langsamer (kein echtes Warten von Sekunden im Test). – der always-Test prüft < 20 s für 8 Versuche; ohne Haken wären es 30 s Pause.
instructions/preflight.md beschreibt den Ausgang des Falls; tools/CONTRACT.md und INSTALL.md (Troubleshooting) sind abgeglichen, soweit sie den Asset-Modus beschreiben; tools/README.md in der Abschlussphase nachgezogen.
PSScriptAnalyzer im Job pwsh ohne Fund (CI-Lauf 602, vorher lokal im CI-Image); test_preflight.py und test_preflight_pwsh.py grün.
Am Windows-Zielsystem (Betreiber, nach dem nächsten Release): Asset preflight.ps1 aus einem echten Release installiert in einen leeren Ordner.Nach #222 verschoben (Betreiberentscheidung 2026-10-13, Abschnitt A dort). Da der Fehler sporadisch ist, belegt ein grüner Lauf allein die Behebung ohnehin nicht; die Tests mit dem Haken sind der eigentliche Nachweis. Unter Linux lief das Asset preflight.sh von v10.0.0 am 2026-10-13 fehlerfrei.
Dateien
tools/preflight.ps1 (Invoke-AssetMode, neu Test-ProtectedName, Get-TargetOccupant, Move-AssetEntry, Undo-AssetMove, Kopfkommentar), tools/preflight.sh (Asset-Modus, neu is_protected, occupants, move_entry, undo_move, Kopfkommentar), tools/chemenu/tests/test_preflight.py, tools/chemenu/tests/test_preflight_pwsh.py, tools/chemenu/tests/conftest.py, instructions/preflight.md, tools/CONTRACT.md (§ Setup; dabei auch die veraltete Angabe „unpack it into chemenu/ next to themselves“ aus der Zeit vor #153 korrigiert), tools/README.md (dieselbe veraltete Angabe, in der Abschlussphase), INSTALL.md § When the agent stops, CHANGES.md.
Version
--patch auf dem 9.0.0-Kandidaten (→ 9.0.0-beta.11, Impact medium; die README-Korrektur → beta.12, Impact low): Fehlerbehebung ohne Schnittstellenänderung, Drop-in in beide Richtungen. Der Testhaken ist eine neue Umgebungsvariable nur für Tests und ändert keine bestehende. Für v8.0.0 gibt es einen belegten Umweg (Lauf 2): Ordner leeren (.git behalten) und das Asset erneut ausführen. Ein Hotfix-Release ist deshalb nicht nötig.
Abhängigkeiten
Keine offenen. Schließt die Lücke, die #153 gegenüber der Handprüfung von #151 geöffnet hat. Die Prüfung von Hand läuft in #222.
Befund aus dem ersten Installationslauf aus einem **echten** Release (v8.0.0, Asset `preflight.ps1` ohne `--archive`) – genau der letzte offene Prüfpunkt von #151. Der erste Lauf ist gescheitert, der zweite nach Leeren des Ordners durchgelaufen: **ein sporadischer Fehler**, der den Nutzer beim ersten Kontakt mit dem Stack zu Handarbeit zwang.
## Stand
**Erledigt (geschlossen 2026-10-13).** Gebaut, veröffentlicht und durch CI bestätigt: 9.0.0-beta.11, Commit `5c9be38` (2026-10-07). CI auf diesem Commit grün: [Lauf 602](https://gitea.nehmer.net/torben/chemenu/actions/runs/602) (Jobs `verify` und `pwsh` – darin PSScriptAnalyzer ohne Fund und die PowerShell- und Preflight-Tests) und [Lauf 603](https://gitea.nehmer.net/torben/chemenu/actions/runs/603). Ausgeliefert mit 9.0.0 und 10.0.0.
**Abschlussphase:** `tools/README.md` beschrieb den Asset-Modus noch aus der Zeit vor #153 (Entpacken nach `<script folder>/chemenu`, vorhandenes Ziel abgewiesen) und kannte weder Wiederholung noch Zurückrollen; nachgezogen in 9.0.0-beta.12, Commit `49f8939`, CI grün: [Lauf 604](https://gitea.nehmer.net/torben/chemenu/actions/runs/604), [Lauf 605](https://gitea.nehmer.net/torben/chemenu/actions/runs/605). INSTALL.md gegen `instructions/preflight.md` erneut gelesen: stimmt überein. `docs/` enthält keine Seite, die den Asset-Modus beschreibt.
**Die Handprüfung am Windows-Zielsystem ist nach #222 verschoben** (Betreiberentscheidung 2026-10-13): Die offenen Prüfungen von Hand nach 10.0.0 sind dort gesammelt, damit sie in einem Termin laufen. Belegt ist der Asset-Weg aus einem echten Release bisher unter Linux (Asset `preflight.sh` von v10.0.0, 2026-10-13: lädt, prüft, entpackt, löscht sich). Der Nachweis der Behebung selbst sind die Tests mit dem Haken (siehe Kriterien), nicht ein einzelner grüner Lauf.
## Was passiert ist (Betreiber, Windows 11, pwsh 7.6.6, GitHub Copilot CLI, 2026-10-07)
**Lauf 1.** Aufforderung: neue Instanz in `C:\Repositories\chemenu` (leer, ohne `.git`), Release über `…/releases/latest`, `setup-instance.md` befolgen, vorher `preflight.md` lesen. Der Agent hat das korrekt getan und `pwsh -NoProfile -ExecutionPolicy Bypass -File preflight.ps1` gestartet:
```
Downloading https://gitea.nehmer.net/torben/chemenu/releases/download/v8.0.0/chemenu-stack-8.0.0.tar.gz
sha256 OK
preflight: could not move tools into C:\Repositories\chemenu - the folder holds part of the stack now; empty it (keep .git) and run this again.
```
Exit 1. Der Agent hat angehalten, nichts gelöscht und nichts wiederholt – das Verhalten des Agenten war richtig, der Fehler lag im Skript. Danach lag laut Betreiber **`tools` im Zielordner**, neben den Ordnern davor; ein `.chemenu-unpack.*` war nicht mehr da. Ob `tools` dort vollständig war, ist nicht mehr feststellbar.
**Diagnoselauf** (Copilot, gleicher Rechner, Defender-Echtzeitschutz an): dreimal derselbe Ablauf wie im Skript – Tarball per `tar -xzf` in `<ziel>\.chemenu-unpack.test\` entpacken, sofort jeden Eintrag per `Move-Item` nach oben verschieben, jede Ausnahme mit Typ, HResult und Dateizahlen protokollieren. **Kein Fehler in drei Läufen** (509, 207, 182 ms für alle Einträge; `tools` jeweils 109 von 109 Dateien).
**Lauf 2** (Ordner vom Betreiber bis auf `preflight.ps1` geleert, sonst nichts geändert): Exit 0, vollständige Installation, Baum-Preflight mit allen Tools `OK` und venv angelegt.
## Analyse gegen den Baum
- Das Asset war bis auf die eingesetzten URLs identisch mit `tools/preflight.ps1` auf `main` (geprüft per `diff`); der Fehler bestand also unverändert im 9.0.0-Kandidaten bis beta.10.
- **Der Ablauf, der gescheitert ist, wurde auf Windows vorher nie geprüft.** Die Handprüfung in #151 (2026-10-01, 8.0.0-beta.17) lief noch mit D40: Entpacken in einen Nachbarordner, dann **eine** Umbenennung des ganzen Ordners. #153 (`a6d07f9`) hat das ersetzt: Ziel ist jetzt der Ordner des Skripts selbst, entpackt wird nach `<ziel>\.chemenu-unpack.<pid>\chemenu-stack-<version>\`, und danach wird **jeder Eintrag einzeln** per `Move-Item` nach oben verschoben (`tools/preflight.ps1` `Invoke-AssetMode`; dasselbe in `tools/preflight.sh` mit `mv`).
- Reihenfolge: `Get-ChildItem` liefert erst Ordner, dann Dateien. Die Ordner im Tarball sind `.claude`, `.github`, `.vibe`, `docs`, `incoming`, `instructions`, `kb`, `raw`, `reports`, **`tools`**, `types`, `work`. Die neun Ordner vor `tools` gingen durch. **`Move-Item` hat bei `tools` etwas am Ziel hinterlassen und trotzdem eine Ausnahme geworfen.** Denkbar ist ein Rückfall auf Kopieren und Löschen, bei dem das Löschen der Quelle an einer Sperre scheitert.
- **Die Ursache wurde verschluckt.** Der `catch` gab `$_.Exception.Message` nicht aus. Mit `$ErrorActionPreference = 'Stop'` landet jede Ausnahme dort – welche es war, lässt sich aus Lauf 1 nicht mehr sagen, und der Diagnoselauf hat den Fehler nicht nachgestellt.
- **Wahrscheinliche Ursache (Hypothese, nicht belegt, aber gestützt):** ein kurzlebiges Datei-Handle im frisch entpackten Baum, typischerweise vom Echtzeitschutz (Defender, auf dem Rechner aktiv) oder einem Indexer. Unter Windows scheitert das Umbenennen eines Ordners, solange irgendeine Datei darin ohne `FILE_SHARE_DELETE` offen ist (`Access denied` / Sharing Violation). `tools/` ist der Ordner mit allen Skripten (`*.ps1`, `*.sh`, `*.py`, sechs ausführbare Dateien) und wird am gründlichsten gescannt. Gestützt wird das dadurch, dass derselbe Ablauf in vier weiteren Läufen (drei Diagnose, ein echter) fehlerfrei war: Der Tarball und der Ablauf sind in Ordnung, der Fehler hängt am Zeitpunkt. Das ist dasselbe Muster, wegen dem npm (`graceful-fs`) `rename` unter Windows bei `EPERM`/`EACCES` bis zu 60 s wiederholt. Der Tarball selbst ist unauffällig: keine Symlinks, keine Sonderrechte, längster Pfad 74 Zeichen.
- Folge: Der Ordner war halb befüllt und damit für jeden neuen Lauf „nicht leer“. Die Meldung schickte den Nutzer zum Leeren von Hand – ein Exit 1, den ein Agent nach `preflight.md` richtigerweise nicht selbst auflöst.
## Entscheidung
### D1 – Nach einem endgültig gescheiterten Verschieben wird zurückgerollt (Betreiber, 2026-10-07)
Das Skript entfernt, was es selbst in diesem Lauf verschoben hat (auch einen Teilrest des gescheiterten Eintrags), und den Entpackordner. Danach liegt im Ordner genau das, was vorher darin lag: `.git` unberührt, das Skript selbst. Meldung: „nothing was installed“, erneut ausführen genügt. Scheitert auch das Zurückrollen, nennt die Meldung die übrig gebliebenen Einträge und sagt wie bisher, dass der Ordner bis auf `.git` geleert werden muss.
**Warum sicher:** Der Ordner war vorher nachweislich leer (bis auf `.git` und das Skript), und entfernt werden nur Namen, die aus dem Archiv stammen und vorher nicht da waren. `.git` und das Skript selbst werden nie angefasst, auch wenn das Archiv einen gleichnamigen Eintrag hätte.
**Warum:** Lauf 2 belegt, dass ein neuer Lauf nach dem Leeren genügt – genau diesen Zustand stellt das Zurückrollen ohne Handarbeit her. Die Zusage „nothing was unpacked/installed“ hält damit für jeden Fehler im Asset-Modus.
Verworfen:
- **Stehen lassen wie bisher** (nur Ursache und Wiederholung): Der Nutzer müsste weiter von Hand leeren, und ein Agent darf das nach `preflight.md` nicht für ihn tun.
- **Direkt mit `tar --strip-components=1` ins Ziel entpacken:** Die Ordner-Umbenennung entfiele, aber ein Fehler *beim Entpacken* (Platte voll, Pfad zu lang) hinterließe einen halben Baum statt „nothing was unpacked“.
### D2 – Wiederholung: Zielrest entfernen und den Eintrag neu aus dem Archiv entpacken (Bau, 2026-10-07)
Der Body ließ offen, wie die Wiederholung die Invariante aus Punkt 2 hält. Entschieden: Jeder Versuch nach dem ersten entfernt zuerst, was vom Eintrag schon am Ziel liegt (nie `.git` oder das Skript), und entpackt dann genau diesen Eintrag neu aus dem Archiv (`tar -xzf <archiv> -C <entpackordner> <top>/<eintrag>`), bevor er verschiebt.
**Warum nicht nur den Zielrest entfernen:** Ein Rückfall auf Kopieren und Löschen, dessen Löschen an einer Sperre scheitert, hinterlässt die Quelle *unvollständig* (schon gelöschte Dateien fehlen dort) und das Ziel vollständig. Den Zielrest wegzuwerfen und die Quelle zu verschieben ergäbe dann einen Eintrag mit fehlenden Dateien, ohne Fehlermeldung. **Warum nicht zusammenführen:** müsste Datei für Datei entscheiden, welche Seite gilt (ein abgebrochenes Kopieren hinterlässt eine abgeschnittene Zieldatei) – in sh und pwsh je eigener Code für einen Fall, den das Neu-Entpacken ohne Fallunterscheidung abdeckt. Das Archiv liegt bis zum Ende des Asset-Modus ohnehin vor.
Der Testhaken bildet diesen Fall nach: `:partial` *verschiebt* einen Teil des Eintrags (Quelle verliert ihn, Ziel hat ihn), statt ihn nur anzulegen. Ein Bau, der nur den Zielrest entfernt, fällt damit im `:partial`-Test durch (per Mutation in beiden Skripten geprüft).
## Was gebaut wurde
Für beide Skripte (`preflight.ps1` und `preflight.sh`, das unter Git Bash auf Windows denselben Schritt macht), Verhalten gleich:
1. **Die Ursache steht in der Meldung.** Scheitert ein Verschieben endgültig, nennt die Zeile den Eintrag und die Fehlermeldung des Systems (pwsh: `$_.Exception.Message`; sh: die stderr-Zeile von `mv`).
2. **Vorübergehende Sperren werden ausgesessen.** Jedes Verschieben wird bei einem Fehler wiederholt, mit wachsender Pause, insgesamt höchstens ca. 30 s je Eintrag. Erst danach gilt es als gescheitert. Die Zahl steht an einer Stelle je Skript (`$MovePauses` / `MOVE_PAUSES` = 1 2 3 4 5 6 9 s, also 8 Versuche).
**Invariante der Wiederholung:** Ein Versuch kann schon einen Teil des Eintrags am Ziel hinterlassen haben (siehe Analyse). Eine Wiederholung darf deshalb nicht blind erneut verschieben – sonst landet der Eintrag als `tools\tools` oder scheitert an „already exists“. Nach der letzten erfolgreichen Wiederholung entspricht der Eintrag am Ziel genau dem im Archiv, und er existiert genau einmal. Wie: D2.
3. **Zurückrollen nach endgültigem Scheitern (D1).** Invariante: Danach enthält der Zielordner genau, was er vor dem Lauf enthielt.
4. **Testhaken** `CHEMENU_PREFLIGHT_MOVE_FAIL=<eintrag>:<n>[:partial]` (Muster wie `CHEMENU_PREFLIGHT_PLATFORM`): das Verschieben von `<eintrag>` scheitert die ersten `<n>` Male (`n` = `always` für dauerhaft); mit `:partial` ist nach jedem gescheiterten Versuch ein Teil des Eintrags schon ans Ziel verschoben. Ist der Haken gesetzt, entfallen die Pausen. Im Kopfkommentar beider Skripte dokumentiert wie die vorhandenen Haken; in `conftest.py` `_WIKITOOL_ENV` eingetragen.
5. `instructions/preflight.md` (Asset-Absatz) beschreibt den Fall: Exit 1 mit „nothing was installed“ – Meldung weitergeben, dann darf der Nutzer das Asset erneut ausführen; meldet das Skript übrig gebliebene Einträge, entscheidet der Nutzer über das Leeren, nicht der Agent.
## Akzeptanzkriterien
Die Testkriterien sind lokal belegt (volle Suite 2568 passed; `test_preflight.py` + `test_preflight_pwsh.py` zusätzlich im CI-Image `chemenu-ci-pwsh` unter dash und pwsh, 182 passed) und durch CI-Lauf 602 bestätigt.
- [x] Mit `CHEMENU_PREFLIGHT_MOVE_FAIL=tools:2` installiert der Asset-Modus vollständig und endet mit dem Exit-Code des Baum-Preflights (beide Skripte). – `test_a_move_that_fails_twice_is_retried_into_a_whole_entry[]`
- [x] Mit `CHEMENU_PREFLIGHT_MOVE_FAIL=tools:2:partial` ebenso, und danach gibt es kein `tools/tools`; die Dateiliste unter `tools/` stimmt mit der im Archiv überein (beide Skripte). – derselbe Test, `[:partial]`
- [x] Mit `CHEMENU_PREFLIGHT_MOVE_FAIL=tools:always` endet er mit Exit 1, und die Fehlerzeile enthält `tools` und die Systemmeldung des letzten Versuchs (beide Skripte). – `test_a_move_that_never_works_is_taken_back` (prüft „attempt 8“)
- [x] Im selben Fall – auch mit `:partial` – enthält der Zielordner danach genau das, was er vorher enthielt: ein vorhandenes `.git` mit unverändertem Inhalt und das Skript selbst, sonst nichts – auch kein `.chemenu-unpack.*`. Ein zweiter Lauf ohne den Haken installiert vollständig. Mit `--into` (Skript liegt außerhalb) enthält das Ziel danach nur noch ein vorhandenes `.git`, bzw. ist leer. Einträge, die *vor* dem gescheiterten verschoben wurden, werden ebenfalls zurückgenommen. – `test_a_move_that_never_works_is_taken_back` (4 Varianten), `test_an_entry_that_moved_before_the_failure_is_taken_back_too`
- [x] Die Testsuite wird durch die Wartezeit nicht spürbar langsamer (kein echtes Warten von Sekunden im Test). – der `always`-Test prüft < 20 s für 8 Versuche; ohne Haken wären es 30 s Pause.
- [x] `instructions/preflight.md` beschreibt den Ausgang des Falls; `tools/CONTRACT.md` und INSTALL.md (Troubleshooting) sind abgeglichen, soweit sie den Asset-Modus beschreiben; `tools/README.md` in der Abschlussphase nachgezogen.
- [x] PSScriptAnalyzer im Job `pwsh` ohne Fund (CI-Lauf 602, vorher lokal im CI-Image); `test_preflight.py` und `test_preflight_pwsh.py` grün.
- [ ] ~~Am Windows-Zielsystem (Betreiber, nach dem nächsten Release): Asset `preflight.ps1` aus einem echten Release installiert in einen leeren Ordner.~~ **Nach #222 verschoben** (Betreiberentscheidung 2026-10-13, Abschnitt A dort). Da der Fehler sporadisch ist, belegt ein grüner Lauf allein die Behebung ohnehin nicht; die Tests mit dem Haken sind der eigentliche Nachweis. Unter Linux lief das Asset `preflight.sh` von v10.0.0 am 2026-10-13 fehlerfrei.
## Dateien
`tools/preflight.ps1` (`Invoke-AssetMode`, neu `Test-ProtectedName`, `Get-TargetOccupant`, `Move-AssetEntry`, `Undo-AssetMove`, Kopfkommentar), `tools/preflight.sh` (Asset-Modus, neu `is_protected`, `occupants`, `move_entry`, `undo_move`, Kopfkommentar), `tools/chemenu/tests/test_preflight.py`, `tools/chemenu/tests/test_preflight_pwsh.py`, `tools/chemenu/tests/conftest.py`, `instructions/preflight.md`, `tools/CONTRACT.md` (§ Setup; dabei auch die veraltete Angabe „unpack it into `chemenu/` next to themselves“ aus der Zeit vor #153 korrigiert), `tools/README.md` (dieselbe veraltete Angabe, in der Abschlussphase), INSTALL.md § When the agent stops, CHANGES.md.
## Version
`--patch` auf dem 9.0.0-Kandidaten (→ 9.0.0-beta.11, Impact medium; die README-Korrektur → beta.12, Impact low): Fehlerbehebung ohne Schnittstellenänderung, Drop-in in beide Richtungen. Der Testhaken ist eine neue Umgebungsvariable nur für Tests und ändert keine bestehende. Für v8.0.0 gibt es einen belegten Umweg (Lauf 2): Ordner leeren (`.git` behalten) und das Asset erneut ausführen. Ein Hotfix-Release ist deshalb nicht nötig.
## Abhängigkeiten
Keine offenen. Schließt die Lücke, die #153 gegenüber der Handprüfung von #151 geöffnet hat. Die Prüfung von Hand läuft in #222.
Changelog: Betreiber-Beobachtung am Zielsystem: Nach dem Lauf lag toolsim Zielordner, ein .chemenu-unpack.* war nicht mehr da. Die Annahme im Body („Copilots Liste passt zu neun Ordnern, tools vermutlich nicht verschoben“) ist damit falsch und wird korrigiert, sobald der Diagnoselauf (Copilot, Move-Experiment mit Ausnahme-Typ/HResult und Dateizahlen; Soll in tools/: 109 Dateien) vorliegt.
**Changelog:** Betreiber-Beobachtung am Zielsystem: Nach dem Lauf lag `tools` **im Zielordner**, ein `.chemenu-unpack.*` war nicht mehr da. Die Annahme im Body („Copilots Liste passt zu neun Ordnern, `tools` vermutlich nicht verschoben“) ist damit falsch und wird korrigiert, sobald der Diagnoselauf (Copilot, Move-Experiment mit Ausnahme-Typ/HResult und Dateizahlen; Soll in `tools/`: 109 Dateien) vorliegt.
Changelog: Diagnoselauf (3× Move-Experiment, kein Fehler) und Lauf 2 (Exit 0, vollständige Installation) eingearbeitet: Der Fehler ist sporadisch, die Sperren-Hypothese gestützt, aber nicht belegt. Teilrest-Invariante und :partial-Haken sind in „Was gebaut wird“ und den Kriterien. prio/blocking → prio/planned: Es gibt einen belegten Umweg (Ordner leeren, erneut ausführen), nichts Laufendes ist blockiert. Hotfix für v8.0.0 deshalb nicht nötig. D1 weiter offen.
**Changelog:** Diagnoselauf (3× Move-Experiment, kein Fehler) und Lauf 2 (Exit 0, vollständige Installation) eingearbeitet: Der Fehler ist sporadisch, die Sperren-Hypothese gestützt, aber nicht belegt. Teilrest-Invariante und `:partial`-Haken sind in „Was gebaut wird“ und den Kriterien. `prio/blocking` → `prio/planned`: Es gibt einen belegten Umweg (Ordner leeren, erneut ausführen), nichts Laufendes ist blockiert. Hotfix für v8.0.0 deshalb nicht nötig. D1 weiter offen.
Changelog: D1 entschieden (Betreiber): Zurückrollen; (b) und (c) als verworfen begründet. „Was gebaut wird“ Punkt 3 und 5 sowie das Rückroll-Kriterium entsprechend fest formuliert, --into-Fall ergänzt. kind/decision → kind/build.
**Changelog:** D1 entschieden (Betreiber): Zurückrollen; (b) und (c) als verworfen begründet. „Was gebaut wird“ Punkt 3 und 5 sowie das Rückroll-Kriterium entsprechend fest formuliert, `--into`-Fall ergänzt. `kind/decision` → `kind/build`.
Changelog: Gebaut und veröffentlicht in 9.0.0-beta.11 (5c9be38), CI grün (Läufe 602, 603). Neu im Body: Abschnitt „Stand“ und D2 – die vom Body offengelassene Wahl, wie die Wiederholung die Invariante hält, ist entschieden: Zielrest entfernen und den Eintrag neu aus dem Archiv entpacken. Begründung: Nur den Zielrest zu entfernen verlöre Dateien, wenn ein Kopieren-und-Löschen die Quelle schon teilweise geleert hat. Zusammenführen hätte Fall-für-Fall-Code in beiden Skripten gebraucht. Kriterien 1–7 abgehakt, mit den belegenden Tests; offen nur noch die Handprüfung unter Windows nach dem nächsten Release. „Was gebaut wird“ heißt jetzt „Was gebaut wurde“. Dateiliste um conftest.py ergänzt und um die Korrektur der veralteten chemenu/-Angabe in tools/CONTRACT.md § Setup.
**Changelog:** Gebaut und veröffentlicht in 9.0.0-beta.11 (`5c9be38`), CI grün (Läufe 602, 603). Neu im Body: Abschnitt „Stand“ und D2 – die vom Body offengelassene Wahl, wie die Wiederholung die Invariante hält, ist entschieden: Zielrest entfernen und den Eintrag neu aus dem Archiv entpacken. Begründung: Nur den Zielrest zu entfernen verlöre Dateien, wenn ein Kopieren-und-Löschen die Quelle schon teilweise geleert hat. Zusammenführen hätte Fall-für-Fall-Code in beiden Skripten gebraucht. Kriterien 1–7 abgehakt, mit den belegenden Tests; offen nur noch die Handprüfung unter Windows nach dem nächsten Release. „Was gebaut wird“ heißt jetzt „Was gebaut wurde“. Dateiliste um `conftest.py` ergänzt und um die Korrektur der veralteten `chemenu/`-Angabe in `tools/CONTRACT.md` § Setup.
Changelog: Abschlussphase. tools/README.md beschrieb den Asset-Modus noch aus der Zeit vor #153; nachgezogen in 9.0.0-beta.12 (49f8939), CI grün (Läufe 604, 605). INSTALL.md gegen preflight.md erneut gelesen: stimmt. Nicht geschlossen: Der Betreiber hat entschieden, dass das Issue bis zur Handprüfung unter Windows nach dem nächsten Release offen bleibt. Neu umgelabelt prio/planned → prio/waiting, weil die Umsetzung fertig ist und nur noch dieser Auslöser fehlt.
**Changelog:** Abschlussphase. `tools/README.md` beschrieb den Asset-Modus noch aus der Zeit vor #153; nachgezogen in 9.0.0-beta.12 (`49f8939`), CI grün (Läufe 604, 605). INSTALL.md gegen `preflight.md` erneut gelesen: stimmt. Nicht geschlossen: Der Betreiber hat entschieden, dass das Issue bis zur Handprüfung unter Windows nach dem nächsten Release offen bleibt. Neu umgelabelt `prio/planned` → `prio/waiting`, weil die Umsetzung fertig ist und nur noch dieser Auslöser fehlt.
Changelog: Geschlossen auf Entscheidung des Betreibers (2026-10-13). Die Handprüfung am Windows-Zielsystem, das letzte Kriterium, ist durchgestrichen und nach #222 (Abschnitt A) verschoben. Dort laufen alle offenen Prüfungen von Hand nach 10.0.0 in einem Termin. Der Code ist seit 9.0.0 ausgeliefert, die Tests mit dem Haken belegen ihn (CI 602–605). Changelog-Zeile: Asset-Preflight wiederholt ein gesperrtes Verschieben, nennt die Ursache und rollt bei endgültigem Scheitern zurück (9.0.0).
**Changelog:** Geschlossen auf Entscheidung des Betreibers (2026-10-13). Die Handprüfung am Windows-Zielsystem, das letzte Kriterium, ist durchgestrichen und nach #222 (Abschnitt A) verschoben. Dort laufen alle offenen Prüfungen von Hand nach 10.0.0 in einem Termin. Der Code ist seit 9.0.0 ausgeliefert, die Tests mit dem Haken belegen ihn (CI 602–605). Changelog-Zeile: Asset-Preflight wiederholt ein gesperrtes Verschieben, nennt die Ursache und rollt bei endgültigem Scheitern zurück (9.0.0).
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Befund aus dem ersten Installationslauf aus einem echten Release (v8.0.0, Asset
preflight.ps1ohne--archive) – genau der letzte offene Prüfpunkt von #151. Der erste Lauf ist gescheitert, der zweite nach Leeren des Ordners durchgelaufen: ein sporadischer Fehler, der den Nutzer beim ersten Kontakt mit dem Stack zu Handarbeit zwang.Stand
Erledigt (geschlossen 2026-10-13). Gebaut, veröffentlicht und durch CI bestätigt: 9.0.0-beta.11, Commit
5c9be38(2026-10-07). CI auf diesem Commit grün: Lauf 602 (Jobsverifyundpwsh– darin PSScriptAnalyzer ohne Fund und die PowerShell- und Preflight-Tests) und Lauf 603. Ausgeliefert mit 9.0.0 und 10.0.0.Abschlussphase:
tools/README.mdbeschrieb den Asset-Modus noch aus der Zeit vor #153 (Entpacken nach<script folder>/chemenu, vorhandenes Ziel abgewiesen) und kannte weder Wiederholung noch Zurückrollen; nachgezogen in 9.0.0-beta.12, Commit49f8939, CI grün: Lauf 604, Lauf 605. INSTALL.md gegeninstructions/preflight.mderneut gelesen: stimmt überein.docs/enthält keine Seite, die den Asset-Modus beschreibt.Die Handprüfung am Windows-Zielsystem ist nach #222 verschoben (Betreiberentscheidung 2026-10-13): Die offenen Prüfungen von Hand nach 10.0.0 sind dort gesammelt, damit sie in einem Termin laufen. Belegt ist der Asset-Weg aus einem echten Release bisher unter Linux (Asset
preflight.shvon v10.0.0, 2026-10-13: lädt, prüft, entpackt, löscht sich). Der Nachweis der Behebung selbst sind die Tests mit dem Haken (siehe Kriterien), nicht ein einzelner grüner Lauf.Was passiert ist (Betreiber, Windows 11, pwsh 7.6.6, GitHub Copilot CLI, 2026-10-07)
Lauf 1. Aufforderung: neue Instanz in
C:\Repositories\chemenu(leer, ohne.git), Release über…/releases/latest,setup-instance.mdbefolgen, vorherpreflight.mdlesen. Der Agent hat das korrekt getan undpwsh -NoProfile -ExecutionPolicy Bypass -File preflight.ps1gestartet:Exit 1. Der Agent hat angehalten, nichts gelöscht und nichts wiederholt – das Verhalten des Agenten war richtig, der Fehler lag im Skript. Danach lag laut Betreiber
toolsim Zielordner, neben den Ordnern davor; ein.chemenu-unpack.*war nicht mehr da. Obtoolsdort vollständig war, ist nicht mehr feststellbar.Diagnoselauf (Copilot, gleicher Rechner, Defender-Echtzeitschutz an): dreimal derselbe Ablauf wie im Skript – Tarball per
tar -xzfin<ziel>\.chemenu-unpack.test\entpacken, sofort jeden Eintrag perMove-Itemnach oben verschieben, jede Ausnahme mit Typ, HResult und Dateizahlen protokollieren. Kein Fehler in drei Läufen (509, 207, 182 ms für alle Einträge;toolsjeweils 109 von 109 Dateien).Lauf 2 (Ordner vom Betreiber bis auf
preflight.ps1geleert, sonst nichts geändert): Exit 0, vollständige Installation, Baum-Preflight mit allen ToolsOKund venv angelegt.Analyse gegen den Baum
tools/preflight.ps1aufmain(geprüft perdiff); der Fehler bestand also unverändert im 9.0.0-Kandidaten bis beta.10.a6d07f9) hat das ersetzt: Ziel ist jetzt der Ordner des Skripts selbst, entpackt wird nach<ziel>\.chemenu-unpack.<pid>\chemenu-stack-<version>\, und danach wird jeder Eintrag einzeln perMove-Itemnach oben verschoben (tools/preflight.ps1Invoke-AssetMode; dasselbe intools/preflight.shmitmv).Get-ChildItemliefert erst Ordner, dann Dateien. Die Ordner im Tarball sind.claude,.github,.vibe,docs,incoming,instructions,kb,raw,reports,tools,types,work. Die neun Ordner vortoolsgingen durch.Move-Itemhat beitoolsetwas am Ziel hinterlassen und trotzdem eine Ausnahme geworfen. Denkbar ist ein Rückfall auf Kopieren und Löschen, bei dem das Löschen der Quelle an einer Sperre scheitert.catchgab$_.Exception.Messagenicht aus. Mit$ErrorActionPreference = 'Stop'landet jede Ausnahme dort – welche es war, lässt sich aus Lauf 1 nicht mehr sagen, und der Diagnoselauf hat den Fehler nicht nachgestellt.FILE_SHARE_DELETEoffen ist (Access denied/ Sharing Violation).tools/ist der Ordner mit allen Skripten (*.ps1,*.sh,*.py, sechs ausführbare Dateien) und wird am gründlichsten gescannt. Gestützt wird das dadurch, dass derselbe Ablauf in vier weiteren Läufen (drei Diagnose, ein echter) fehlerfrei war: Der Tarball und der Ablauf sind in Ordnung, der Fehler hängt am Zeitpunkt. Das ist dasselbe Muster, wegen dem npm (graceful-fs)renameunter Windows beiEPERM/EACCESbis zu 60 s wiederholt. Der Tarball selbst ist unauffällig: keine Symlinks, keine Sonderrechte, längster Pfad 74 Zeichen.preflight.mdrichtigerweise nicht selbst auflöst.Entscheidung
D1 – Nach einem endgültig gescheiterten Verschieben wird zurückgerollt (Betreiber, 2026-10-07)
Das Skript entfernt, was es selbst in diesem Lauf verschoben hat (auch einen Teilrest des gescheiterten Eintrags), und den Entpackordner. Danach liegt im Ordner genau das, was vorher darin lag:
.gitunberührt, das Skript selbst. Meldung: „nothing was installed“, erneut ausführen genügt. Scheitert auch das Zurückrollen, nennt die Meldung die übrig gebliebenen Einträge und sagt wie bisher, dass der Ordner bis auf.gitgeleert werden muss.Warum sicher: Der Ordner war vorher nachweislich leer (bis auf
.gitund das Skript), und entfernt werden nur Namen, die aus dem Archiv stammen und vorher nicht da waren..gitund das Skript selbst werden nie angefasst, auch wenn das Archiv einen gleichnamigen Eintrag hätte.Warum: Lauf 2 belegt, dass ein neuer Lauf nach dem Leeren genügt – genau diesen Zustand stellt das Zurückrollen ohne Handarbeit her. Die Zusage „nothing was unpacked/installed“ hält damit für jeden Fehler im Asset-Modus.
Verworfen:
preflight.mdnicht für ihn tun.tar --strip-components=1ins Ziel entpacken: Die Ordner-Umbenennung entfiele, aber ein Fehler beim Entpacken (Platte voll, Pfad zu lang) hinterließe einen halben Baum statt „nothing was unpacked“.D2 – Wiederholung: Zielrest entfernen und den Eintrag neu aus dem Archiv entpacken (Bau, 2026-10-07)
Der Body ließ offen, wie die Wiederholung die Invariante aus Punkt 2 hält. Entschieden: Jeder Versuch nach dem ersten entfernt zuerst, was vom Eintrag schon am Ziel liegt (nie
.gitoder das Skript), und entpackt dann genau diesen Eintrag neu aus dem Archiv (tar -xzf <archiv> -C <entpackordner> <top>/<eintrag>), bevor er verschiebt.Warum nicht nur den Zielrest entfernen: Ein Rückfall auf Kopieren und Löschen, dessen Löschen an einer Sperre scheitert, hinterlässt die Quelle unvollständig (schon gelöschte Dateien fehlen dort) und das Ziel vollständig. Den Zielrest wegzuwerfen und die Quelle zu verschieben ergäbe dann einen Eintrag mit fehlenden Dateien, ohne Fehlermeldung. Warum nicht zusammenführen: müsste Datei für Datei entscheiden, welche Seite gilt (ein abgebrochenes Kopieren hinterlässt eine abgeschnittene Zieldatei) – in sh und pwsh je eigener Code für einen Fall, den das Neu-Entpacken ohne Fallunterscheidung abdeckt. Das Archiv liegt bis zum Ende des Asset-Modus ohnehin vor.
Der Testhaken bildet diesen Fall nach:
:partialverschiebt einen Teil des Eintrags (Quelle verliert ihn, Ziel hat ihn), statt ihn nur anzulegen. Ein Bau, der nur den Zielrest entfernt, fällt damit im:partial-Test durch (per Mutation in beiden Skripten geprüft).Was gebaut wurde
Für beide Skripte (
preflight.ps1undpreflight.sh, das unter Git Bash auf Windows denselben Schritt macht), Verhalten gleich:$_.Exception.Message; sh: die stderr-Zeile vonmv).$MovePauses/MOVE_PAUSES= 1 2 3 4 5 6 9 s, also 8 Versuche).Invariante der Wiederholung: Ein Versuch kann schon einen Teil des Eintrags am Ziel hinterlassen haben (siehe Analyse). Eine Wiederholung darf deshalb nicht blind erneut verschieben – sonst landet der Eintrag als
tools\toolsoder scheitert an „already exists“. Nach der letzten erfolgreichen Wiederholung entspricht der Eintrag am Ziel genau dem im Archiv, und er existiert genau einmal. Wie: D2.CHEMENU_PREFLIGHT_MOVE_FAIL=<eintrag>:<n>[:partial](Muster wieCHEMENU_PREFLIGHT_PLATFORM): das Verschieben von<eintrag>scheitert die ersten<n>Male (n=alwaysfür dauerhaft); mit:partialist nach jedem gescheiterten Versuch ein Teil des Eintrags schon ans Ziel verschoben. Ist der Haken gesetzt, entfallen die Pausen. Im Kopfkommentar beider Skripte dokumentiert wie die vorhandenen Haken; inconftest.py_WIKITOOL_ENVeingetragen.instructions/preflight.md(Asset-Absatz) beschreibt den Fall: Exit 1 mit „nothing was installed“ – Meldung weitergeben, dann darf der Nutzer das Asset erneut ausführen; meldet das Skript übrig gebliebene Einträge, entscheidet der Nutzer über das Leeren, nicht der Agent.Akzeptanzkriterien
Die Testkriterien sind lokal belegt (volle Suite 2568 passed;
test_preflight.py+test_preflight_pwsh.pyzusätzlich im CI-Imagechemenu-ci-pwshunter dash und pwsh, 182 passed) und durch CI-Lauf 602 bestätigt.CHEMENU_PREFLIGHT_MOVE_FAIL=tools:2installiert der Asset-Modus vollständig und endet mit dem Exit-Code des Baum-Preflights (beide Skripte). –test_a_move_that_fails_twice_is_retried_into_a_whole_entry[]CHEMENU_PREFLIGHT_MOVE_FAIL=tools:2:partialebenso, und danach gibt es keintools/tools; die Dateiliste untertools/stimmt mit der im Archiv überein (beide Skripte). – derselbe Test,[:partial]CHEMENU_PREFLIGHT_MOVE_FAIL=tools:alwaysendet er mit Exit 1, und die Fehlerzeile enthälttoolsund die Systemmeldung des letzten Versuchs (beide Skripte). –test_a_move_that_never_works_is_taken_back(prüft „attempt 8“):partial– enthält der Zielordner danach genau das, was er vorher enthielt: ein vorhandenes.gitmit unverändertem Inhalt und das Skript selbst, sonst nichts – auch kein.chemenu-unpack.*. Ein zweiter Lauf ohne den Haken installiert vollständig. Mit--into(Skript liegt außerhalb) enthält das Ziel danach nur noch ein vorhandenes.git, bzw. ist leer. Einträge, die vor dem gescheiterten verschoben wurden, werden ebenfalls zurückgenommen. –test_a_move_that_never_works_is_taken_back(4 Varianten),test_an_entry_that_moved_before_the_failure_is_taken_back_tooalways-Test prüft < 20 s für 8 Versuche; ohne Haken wären es 30 s Pause.instructions/preflight.mdbeschreibt den Ausgang des Falls;tools/CONTRACT.mdund INSTALL.md (Troubleshooting) sind abgeglichen, soweit sie den Asset-Modus beschreiben;tools/README.mdin der Abschlussphase nachgezogen.pwshohne Fund (CI-Lauf 602, vorher lokal im CI-Image);test_preflight.pyundtest_preflight_pwsh.pygrün.Am Windows-Zielsystem (Betreiber, nach dem nächsten Release): AssetNach #222 verschoben (Betreiberentscheidung 2026-10-13, Abschnitt A dort). Da der Fehler sporadisch ist, belegt ein grüner Lauf allein die Behebung ohnehin nicht; die Tests mit dem Haken sind der eigentliche Nachweis. Unter Linux lief das Assetpreflight.ps1aus einem echten Release installiert in einen leeren Ordner.preflight.shvon v10.0.0 am 2026-10-13 fehlerfrei.Dateien
tools/preflight.ps1(Invoke-AssetMode, neuTest-ProtectedName,Get-TargetOccupant,Move-AssetEntry,Undo-AssetMove, Kopfkommentar),tools/preflight.sh(Asset-Modus, neuis_protected,occupants,move_entry,undo_move, Kopfkommentar),tools/chemenu/tests/test_preflight.py,tools/chemenu/tests/test_preflight_pwsh.py,tools/chemenu/tests/conftest.py,instructions/preflight.md,tools/CONTRACT.md(§ Setup; dabei auch die veraltete Angabe „unpack it intochemenu/next to themselves“ aus der Zeit vor #153 korrigiert),tools/README.md(dieselbe veraltete Angabe, in der Abschlussphase), INSTALL.md § When the agent stops, CHANGES.md.Version
--patchauf dem 9.0.0-Kandidaten (→ 9.0.0-beta.11, Impact medium; die README-Korrektur → beta.12, Impact low): Fehlerbehebung ohne Schnittstellenänderung, Drop-in in beide Richtungen. Der Testhaken ist eine neue Umgebungsvariable nur für Tests und ändert keine bestehende. Für v8.0.0 gibt es einen belegten Umweg (Lauf 2): Ordner leeren (.gitbehalten) und das Asset erneut ausführen. Ein Hotfix-Release ist deshalb nicht nötig.Abhängigkeiten
Keine offenen. Schließt die Lücke, die #153 gegenüber der Handprüfung von #151 geöffnet hat. Die Prüfung von Hand läuft in #222.
Changelog: Betreiber-Beobachtung am Zielsystem: Nach dem Lauf lag
toolsim Zielordner, ein.chemenu-unpack.*war nicht mehr da. Die Annahme im Body („Copilots Liste passt zu neun Ordnern,toolsvermutlich nicht verschoben“) ist damit falsch und wird korrigiert, sobald der Diagnoselauf (Copilot, Move-Experiment mit Ausnahme-Typ/HResult und Dateizahlen; Soll intools/: 109 Dateien) vorliegt.Changelog: Diagnoselauf (3× Move-Experiment, kein Fehler) und Lauf 2 (Exit 0, vollständige Installation) eingearbeitet: Der Fehler ist sporadisch, die Sperren-Hypothese gestützt, aber nicht belegt. Teilrest-Invariante und
:partial-Haken sind in „Was gebaut wird“ und den Kriterien.prio/blocking→prio/planned: Es gibt einen belegten Umweg (Ordner leeren, erneut ausführen), nichts Laufendes ist blockiert. Hotfix für v8.0.0 deshalb nicht nötig. D1 weiter offen.Changelog: D1 entschieden (Betreiber): Zurückrollen; (b) und (c) als verworfen begründet. „Was gebaut wird“ Punkt 3 und 5 sowie das Rückroll-Kriterium entsprechend fest formuliert,
--into-Fall ergänzt.kind/decision→kind/build.Changelog: Gebaut und veröffentlicht in 9.0.0-beta.11 (
5c9be38), CI grün (Läufe 602, 603). Neu im Body: Abschnitt „Stand“ und D2 – die vom Body offengelassene Wahl, wie die Wiederholung die Invariante hält, ist entschieden: Zielrest entfernen und den Eintrag neu aus dem Archiv entpacken. Begründung: Nur den Zielrest zu entfernen verlöre Dateien, wenn ein Kopieren-und-Löschen die Quelle schon teilweise geleert hat. Zusammenführen hätte Fall-für-Fall-Code in beiden Skripten gebraucht. Kriterien 1–7 abgehakt, mit den belegenden Tests; offen nur noch die Handprüfung unter Windows nach dem nächsten Release. „Was gebaut wird“ heißt jetzt „Was gebaut wurde“. Dateiliste umconftest.pyergänzt und um die Korrektur der veraltetenchemenu/-Angabe intools/CONTRACT.md§ Setup.Changelog: Abschlussphase.
tools/README.mdbeschrieb den Asset-Modus noch aus der Zeit vor #153; nachgezogen in 9.0.0-beta.12 (49f8939), CI grün (Läufe 604, 605). INSTALL.md gegenpreflight.mderneut gelesen: stimmt. Nicht geschlossen: Der Betreiber hat entschieden, dass das Issue bis zur Handprüfung unter Windows nach dem nächsten Release offen bleibt. Neu umgelabeltprio/planned→prio/waiting, weil die Umsetzung fertig ist und nur noch dieser Auslöser fehlt.torben referenced this issue2026-10-13 19:45:51 +00:00
Changelog: Geschlossen auf Entscheidung des Betreibers (2026-10-13). Die Handprüfung am Windows-Zielsystem, das letzte Kriterium, ist durchgestrichen und nach #222 (Abschnitt A) verschoben. Dort laufen alle offenen Prüfungen von Hand nach 10.0.0 in einem Termin. Der Code ist seit 9.0.0 ausgeliefert, die Tests mit dem Haken belegen ihn (CI 602–605). Changelog-Zeile: Asset-Preflight wiederholt ein gesperrtes Verschieben, nennt die Ursache und rollt bei endgültigem Scheitern zurück (9.0.0).