Umgesetzt und publiziert am 2026-09-16, 6.1.0-beta.5 (--minor, --impact high).
e4e2332 - die Aenderung selbst (15 Dateien, +610/-44), nach Freigabe des Mass-Update-Gates.
4284f10 - Nachzug in docs/why-gates-are-code.md (kein Bump: docs/ liegt nicht im
Pfad-Scope des CI-Version-Gates).
Der Befund ist geschlossen: eine Sitzung auf Claude Code laeuft jetzt in einem Bucket, und das
Iteration-Budget-Gate zaehlt ueber einen ganzen Lauf. Der Beleg steht unter § Verifiziert.
Was der Befund war
chemenu.session fiel ohne gesetztes WIKITOOL_SESSION_ID auf os.getppid() zurueck. Auf Claude
Code - jeder Bash-Tool-Call in einer frisch initialisierten Shell - war das eine neue "Sitzung" pro
Aufruf. Gemessen in #107: ein Lauf mit 33 wikitool-Aufrufen erschien als 21 Telemetrie-Buckets;
der hoechste PID-Bucket kam auf 3 von 60 moeglichen Aufrufen. Zwei Folgen, beide behoben:
Der Join-Key hielt nicht. Hook-Events trugen die Harness-UUID, wikitool-Events die
wechselnde PID. EVALS.mds Satz "Everything joins on WIKITOOL_SESSION_ID" war auf dem
Hauptharness falsch, und die L2-Regel clearance-ended-the-turn meldete "cannot say", obwohl
der Harness prompt.submitted fuenfmal gemeldet hatte.
Das Iteration-Budget-Gate war strukturell unerreichbar, und der Loop-Breaker mit ihm - drei
identische Aufrufe landeten in drei Buckets.
Entscheidungen (getroffen 2026-09-16, umgesetzt wie beschlossen)
Fallback-Kette WIKITOOL_SESSION_ID → Registry belegter Harness-Variablen → getppid().
Umgesetzt in chemenu/session.py als HARNESS_ENV_VARS mit einem verifizierten Eintrag, CLAUDE_CODE_SESSION_ID → claude-code. Der Wert wird unveraendert Schluessel - kein
Praefix, keine Normalisierung ausser dem bestehenden session_slug() fuer Verzeichnisnamen,
weil genau diese Gleichheit den Join mit den Hook-Events herstellt. Verworfen blieben: eine
Sitzungsdatei im Checkout und eine Branch/Worktree-Kette.
doctor ist dreiwertig, die Herkunft steht in Trace und Status. Kein zusaetzlicher
Meldepunkt in publish - verworfen als Rauschen.
Limit bleibt 60, Loop-Window 3. Nicht vorsorglich angehoben: das haette das Gate an einer
Zahl kalibriert, fuer die es weiterhin keine Messung gibt. Die erste Sitzung, die es reisst,
ist die Messung - dann als eigenes Issue.
Der SIGPIPE-Nebenbefund wurde hier mitgemacht.
Versionsteil --minor. Gegen instructions/dev/version-parts.md geprueft: vorwaerts ein
Drop-in (die Kette faellt weiterhin auf getppid() zurueck, budget.json wird unveraendert
weitergelesen, Eintraege ohne Herkunftsfeld behalten ihren Count), rueckwaerts ebenso (die alte
Fassung liest count/recent/last_seen und ignoriert das zusaetzliche Feld). Kein
Kommando-, Flag- oder Variablen-Rename; WIKITOOL_SESSION_ID behaelt Namen und Vorrang. Der
Vorbehalt aus der Planung - Praefix oder Pflichtfeld waere ein Grenzuebertritt - ist geprueft
und traf nicht zu.
Was gebaut wurde
chemenu/session.py - die dreistufige Kette, session_id_source() nennt im Harness-Fall
die Variable und den Harness (CLAUDE_CODE_SESSION_ID (claude-code)). Der Modulkopf haelt
fest, dass ein Registry-Eintrag nur nach Messung gegen eine echte Sitzung aufgenommen wird.
commands/run_budget.py - _entry_for(): jeder Eintrag traegt seine Herkunft; dieselbe
Id-Zeichenkette unter anderer Herkunft beginnt einen neuen Zaehler statt einen fremden zu
erben. Eintraege ohne das Feld (vor der Aenderung geschrieben) behalten ihren Count.
commands/doctor.py - check_session_id() dreiwertig: OK bei expliziter Variable, OK
mit Nennung des Harness bei einer Registry-Variablen, WARN nur noch beim PID-Fallback.
telemetry/writer.py - der session.start-Event der wikitool-Quelle traegt session_origin. Nur diese Quelle, weil nur sie ihre Id ueber die Kette aufloest; jede andere
bekommt die Id aus ihrer eigenen Hook-Payload gereicht.
cli.py (SIGPIPE) - gemessen, was die Planung offen gelassen hatte: Click faengt BrokenPipeError selbst ab (core.py, except OSError ... errno.EPIPE) und erzwingt sys.exit(1); ununterscheidbar von einem echten Fehlschlag, und deshalb nachtraeglich nicht
mehr korrigierbar. _BrokenPipeSwallow sitzt jetzt vor Click auf stdout/stderr und
schluckt den EPIPE-Schreibfehler, bevor Click ihn sieht; der Aufruf endet mit seinem echten
Exit-Code und der Trace-Eintrag traegt stdout_truncated: true. _pacify_real_fd() haengt den
echten Deskriptor danach an /dev/null, sonst meldet CPythons eigener Flush beim
Interpreter-Ende "Exception ignored while flushing sys.stdout" und der Prozess endet mit 120.
Tests - tests/test_cli.py ist neu (9 Tests); conftest.pys _WIKITOOL_ENV zieht die
Registry-Variablen aus HARNESS_ENV_VARS, weil diese Suite in Claude Code laeuft und sonst
jeder Fallback-Test die echte Sitzungs-Id eingesammelt haette. Die beiden Gate-Tests laufen
ueber echte Subprozesse, weil os.getppid() innerhalb eines Testprozesses konstant ist.
Doku-Nachzug - EVALS.md (Join-Satz, session_id-Feldzeile, plus die Feststellung, dass
ein beobachtender Tool-Hook ohne gemeinsamen Schluessel nichts loest - ohne Issue-Nummer, das
Dokument wird ausgeliefert), instructions/session-setup.md, INSTALL.md, tools/CONTRACT.md
(doctor-Zeile und Budget-Abschnitt), docs/why-gates-are-code.md.
version bump --minor --impact high samt Changeset-Prosa in CHANGES.md.
Akzeptanzkriterien
Ein Lauf auf Claude Code landet in genau einem Telemetrie-Bucket. Belegt an der echten
Sitzung 30d734d5-7f75-4890-b17e-1ce930f381fc: drei wikitool-Aufrufe aus drei
verschiedenen Shells (Shell-PIDs 4035563, 4035575 und die des Publish) stehen in einem Bucket in budget.json (count: 3, source: CLAUDE_CODE_SESSION_ID (claude-code)) und in einem Trace-Verzeichnis. Vor der Aenderung waeren das drei
Buckets mit je 1 gewesen. Siehe § Verifiziert fuer die Einschraenkung auf der Hook-Seite.
EVALS.mds Join-Satz ist ersetzt. Er beschreibt jetzt die Kette und die Bedingung,
unter der ein Harness einen Registry-Eintrag bekommt; die session_id-Feldzeile stimmt mit session_id_source() ueberein.
Das Gate zaehlt ueber einen ganzen Lauf.test_the_iteration_budget_gate_trips_across_separate_shells
setzt 61 Aufrufe aus je eigenem Subprozess ab, nur ueber die Harness-Variable - der 61. wird
verweigert. test_the_loop_breaker_trips_across_separate_shells analog fuer drei identische
Aufrufe. Beide Tests waeren vor dieser Aenderung gruen und blind gewesen.
Kein bestehender Bucket wird still neu interpretiert. Drei Tests in test_run_budget.py: ein Bucket ohne Herkunftsfeld behaelt seinen Count (Altbestand), einer
mit abweichender Herkunft beginnt bei 0, einer mit gleicher Herkunft zaehlt weiter.
doctor dreiwertig, Herkunft in budget status und im session.start-Event. Live
geprueft: ohne WIKITOOL_SESSION_ID meldet doctorOK session-id: scoped by harness variable CLAUDE_CODE_SESSION_ID (claude-code) und budget status nennt dieselbe Herkunft.
Ein durch eine geschlossene Pipe abgebrochener Aufruf steht nicht als Fehler in der
Trace. Vorher/nachher am selben Kommando gemessen: types describe source | head -1
stand mit exit_code: 1 in der Trace, jetzt mit exit_code: 0 und stdout_truncated: true, ohne die Meldung des CPython-Flushs und mit Prozess-Exit 0 statt 120. Ein echter
Fehlschlag (new entity --name "") steht weiterhin mit exit_code: 1 und ohne das Flag.
Das Verhaeltnis zum Hook-Issue ist dokumentiert. In EVALS.md als Sachaussage ohne
Nummer, und als Kommentar an #82.
CI: Laeufe 303 und 304 zu e4e2332 - beide success, inklusive Version-Gate und des
vollstaendigen setup-instance.md-Replays gegen einen frischen dist export. Lauf 305 zu 4284f10 (nur docs/) lief zum Zeitpunkt des Schliessens noch.
Live auf dieser Instanz gemessen, mit und ohne gesetzte Variable: doctor, budget status, budget.json, Trace-Inhalt.
Einschraenkung, die zum Nachweis oben gehoert: das Trace-Verzeichnis dieser Sitzung enthaelt
nur noch die wikitool-Events. Die Umsetzungssitzung hat es bei einer Aufraeumaktion zwischen
zwei Pruefschritten selbst geloescht (rm -rf reports/telemetry/$CLAUDE_CODE_SESSION_ID) und damit
die beiden Hook-Events dieser Sitzung mitgenommen - gitignorierte, abgeleitete Daten, aber eben
auch der Beleg. Die Gleichheit der Schluessel ist trotzdem belegt: dieselbe Datei wurde zu Beginn
derselben Sitzung gelesen und trug dort "session_id":"30d734d5-7f75-4890-b17e-1ce930f381fc" in session.start und prompt.submitted aus der Quelle claude-code - also genau den Wert, unter
dem die wikitool-Events jetzt stehen. Eine Trace, die beide Seiten in einem Schnappschuss zeigt,
entsteht bei der naechsten Prompt-Abgabe dieser oder jeder folgenden Sitzung von selbst.
Bewusst nicht gemacht
kb/concepts/workflows/Iteration and Cost Limits.md nicht nachgezogen. Die Seite nennt
weiter nur WIKITOOL_SESSION_ID und die PID als Fallback. Eine kb/-Seite braucht eine Quelle
(Invariante 3), und fuer diese Aenderung existiert keine in raw/; eine zu schreiben ist
Inhaltsarbeit nach den kb/-Regeln und nicht Teil dieses Werkpakets. Wer den naechsten
Stack-Lauf ohnehin als Quelle ablegt, zieht sie dabei mit.
Das Limit nicht angehoben (Entscheidung 3).
Kein beobachtender Tool-Hook verdrahtet - das ist #82 und bleibt dort; dieses Issue hat nur
die Reihenfolge geklaert.
Herkunft
Befund 1 aus #107 - dem getraceten Upgrade-Lauf 5.0.0 auf 6.0.0 auf einer echten
ausgelieferten Instanz, 2026-09-15, Harness Claude Code, Sitzung 671c1b9a. Sechs der sieben
Befunde waren dort erledigt; dieser war der einzige, der eine Betreiberentscheidung brauchte.
Ausgelagert am 2026-09-16, entschieden und umgesetzt am selben Tag.
Die Evidenz des urspruenglichen Befunds bleibt in #107: die 63-Event-Trace verbatim im ersten
Kommentar, die Bucket-Messung und die Scorer-Ausgabe im Befund-1-Abschnitt, und der
Reproduktionsabschnitt, dessen Schritt 5 genau diesen Befund nachstellt - er reproduziert nach e4e2332 nicht mehr.
Modelle dieser Sitzung (kein mechanischer Check deckt eine dieser drei Phasen ab): Entwurf,
Versionsteil und Grenzurteil auf Opus 5; Code, Tests und Bump ebenfalls auf Opus 5 (der Wechsel
auf Sonnet wurde angeboten und nicht genommen); Abschlussphase auf Opus 5.
## Ergebnis
**Umgesetzt und publiziert am 2026-09-16**, `6.1.0-beta.5` (`--minor`, `--impact high`).
- `e4e2332` - die Aenderung selbst (15 Dateien, +610/-44), nach Freigabe des Mass-Update-Gates.
- `4284f10` - Nachzug in `docs/why-gates-are-code.md` (kein Bump: `docs/` liegt nicht im
Pfad-Scope des CI-Version-Gates).
Der Befund ist geschlossen: eine Sitzung auf Claude Code laeuft jetzt in **einem** Bucket, und das
Iteration-Budget-Gate zaehlt ueber einen ganzen Lauf. Der Beleg steht unter § Verifiziert.
## Was der Befund war
`chemenu.session` fiel ohne gesetztes `WIKITOOL_SESSION_ID` auf `os.getppid()` zurueck. Auf Claude
Code - jeder Bash-Tool-Call in einer frisch initialisierten Shell - war das eine neue "Sitzung" pro
Aufruf. Gemessen in #107: ein Lauf mit 33 `wikitool`-Aufrufen erschien als 21 Telemetrie-Buckets;
der hoechste PID-Bucket kam auf 3 von 60 moeglichen Aufrufen. Zwei Folgen, beide behoben:
1. **Der Join-Key hielt nicht.** Hook-Events trugen die Harness-UUID, `wikitool`-Events die
wechselnde PID. `EVALS.md`s Satz *"Everything joins on `WIKITOOL_SESSION_ID`"* war auf dem
Hauptharness falsch, und die L2-Regel `clearance-ended-the-turn` meldete "cannot say", obwohl
der Harness `prompt.submitted` fuenfmal gemeldet hatte.
2. **Das Iteration-Budget-Gate war strukturell unerreichbar**, und der Loop-Breaker mit ihm - drei
identische Aufrufe landeten in drei Buckets.
## Entscheidungen (getroffen 2026-09-16, umgesetzt wie beschlossen)
1. **Fallback-Kette `WIKITOOL_SESSION_ID` → Registry belegter Harness-Variablen → `getppid()`.**
Umgesetzt in `chemenu/session.py` als `HARNESS_ENV_VARS` mit einem verifizierten Eintrag,
`CLAUDE_CODE_SESSION_ID` → `claude-code`. Der Wert wird **unveraendert** Schluessel - kein
Praefix, keine Normalisierung ausser dem bestehenden `session_slug()` fuer Verzeichnisnamen,
weil genau diese Gleichheit den Join mit den Hook-Events herstellt. Verworfen blieben: eine
Sitzungsdatei im Checkout und eine Branch/Worktree-Kette.
2. **`doctor` ist dreiwertig**, die Herkunft steht in Trace und Status. Kein zusaetzlicher
Meldepunkt in `publish` - verworfen als Rauschen.
3. **Limit bleibt 60, Loop-Window 3.** Nicht vorsorglich angehoben: das haette das Gate an einer
Zahl kalibriert, fuer die es weiterhin keine Messung gibt. Die erste Sitzung, die es reisst,
ist die Messung - dann als eigenes Issue.
4. **Der SIGPIPE-Nebenbefund wurde hier mitgemacht.**
5. **Versionsteil `--minor`.** Gegen `instructions/dev/version-parts.md` geprueft: vorwaerts ein
Drop-in (die Kette faellt weiterhin auf `getppid()` zurueck, `budget.json` wird unveraendert
weitergelesen, Eintraege ohne Herkunftsfeld behalten ihren Count), rueckwaerts ebenso (die alte
Fassung liest `count`/`recent`/`last_seen` und ignoriert das zusaetzliche Feld). Kein
Kommando-, Flag- oder Variablen-Rename; `WIKITOOL_SESSION_ID` behaelt Namen und Vorrang. Der
Vorbehalt aus der Planung - Praefix oder Pflichtfeld waere ein Grenzuebertritt - ist geprueft
und traf nicht zu.
## Was gebaut wurde
1. **`chemenu/session.py`** - die dreistufige Kette, `session_id_source()` nennt im Harness-Fall
die Variable und den Harness (`CLAUDE_CODE_SESSION_ID (claude-code)`). Der Modulkopf haelt
fest, dass ein Registry-Eintrag nur nach Messung gegen eine echte Sitzung aufgenommen wird.
2. **`commands/run_budget.py`** - `_entry_for()`: jeder Eintrag traegt seine Herkunft; dieselbe
Id-Zeichenkette unter anderer Herkunft beginnt einen neuen Zaehler statt einen fremden zu
erben. Eintraege ohne das Feld (vor der Aenderung geschrieben) behalten ihren Count.
3. **`commands/doctor.py`** - `check_session_id()` dreiwertig: `OK` bei expliziter Variable, `OK`
mit Nennung des Harness bei einer Registry-Variablen, `WARN` nur noch beim PID-Fallback.
4. **`telemetry/writer.py`** - der `session.start`-Event der `wikitool`-Quelle traegt
`session_origin`. Nur diese Quelle, weil nur sie ihre Id ueber die Kette aufloest; jede andere
bekommt die Id aus ihrer eigenen Hook-Payload gereicht.
5. **`cli.py` (SIGPIPE)** - gemessen, was die Planung offen gelassen hatte: Click faengt
`BrokenPipeError` selbst ab (`core.py`, `except OSError ... errno.EPIPE`) und erzwingt
`sys.exit(1)`; ununterscheidbar von einem echten Fehlschlag, und deshalb nachtraeglich nicht
mehr korrigierbar. `_BrokenPipeSwallow` sitzt jetzt **vor** Click auf `stdout`/`stderr` und
schluckt den EPIPE-Schreibfehler, bevor Click ihn sieht; der Aufruf endet mit seinem echten
Exit-Code und der Trace-Eintrag traegt `stdout_truncated: true`. `_pacify_real_fd()` haengt den
echten Deskriptor danach an `/dev/null`, sonst meldet CPythons eigener Flush beim
Interpreter-Ende "Exception ignored while flushing sys.stdout" und der Prozess endet mit 120.
6. **Tests** - `tests/test_cli.py` ist neu (9 Tests); `conftest.py`s `_WIKITOOL_ENV` zieht die
Registry-Variablen aus `HARNESS_ENV_VARS`, weil diese Suite **in** Claude Code laeuft und sonst
jeder Fallback-Test die echte Sitzungs-Id eingesammelt haette. Die beiden Gate-Tests laufen
ueber echte Subprozesse, weil `os.getppid()` innerhalb eines Testprozesses konstant ist.
7. **Doku-Nachzug** - `EVALS.md` (Join-Satz, `session_id`-Feldzeile, plus die Feststellung, dass
ein beobachtender Tool-Hook ohne gemeinsamen Schluessel nichts loest - ohne Issue-Nummer, das
Dokument wird ausgeliefert), `instructions/session-setup.md`, `INSTALL.md`, `tools/CONTRACT.md`
(`doctor`-Zeile und Budget-Abschnitt), `docs/why-gates-are-code.md`.
8. **`version bump --minor --impact high`** samt Changeset-Prosa in `CHANGES.md`.
## Akzeptanzkriterien
- [x] **Ein Lauf auf Claude Code landet in genau einem Telemetrie-Bucket.** Belegt an der echten
Sitzung `30d734d5-7f75-4890-b17e-1ce930f381fc`: drei `wikitool`-Aufrufe aus **drei
verschiedenen Shells** (Shell-PIDs 4035563, 4035575 und die des Publish) stehen in
**einem** Bucket in `budget.json` (`count: 3`, `source: CLAUDE_CODE_SESSION_ID
(claude-code)`) und in **einem** Trace-Verzeichnis. Vor der Aenderung waeren das drei
Buckets mit je 1 gewesen. Siehe § Verifiziert fuer die Einschraenkung auf der Hook-Seite.
- [x] **`EVALS.md`s Join-Satz ist ersetzt.** Er beschreibt jetzt die Kette und die Bedingung,
unter der ein Harness einen Registry-Eintrag bekommt; die `session_id`-Feldzeile stimmt mit
`session_id_source()` ueberein.
- [x] **Das Gate zaehlt ueber einen ganzen Lauf.** `test_the_iteration_budget_gate_trips_across_separate_shells`
setzt 61 Aufrufe aus je eigenem Subprozess ab, nur ueber die Harness-Variable - der 61. wird
verweigert. `test_the_loop_breaker_trips_across_separate_shells` analog fuer drei identische
Aufrufe. Beide Tests waeren vor dieser Aenderung gruen und blind gewesen.
- [x] **Kein bestehender Bucket wird still neu interpretiert.** Drei Tests in
`test_run_budget.py`: ein Bucket ohne Herkunftsfeld behaelt seinen Count (Altbestand), einer
mit abweichender Herkunft beginnt bei 0, einer mit gleicher Herkunft zaehlt weiter.
- [x] **`doctor` dreiwertig, Herkunft in `budget status` und im `session.start`-Event.** Live
geprueft: ohne `WIKITOOL_SESSION_ID` meldet `doctor` `OK session-id: scoped by harness
variable CLAUDE_CODE_SESSION_ID (claude-code)` und `budget status` nennt dieselbe Herkunft.
- [x] **Ein durch eine geschlossene Pipe abgebrochener Aufruf steht nicht als Fehler in der
Trace.** Vorher/nachher am selben Kommando gemessen: `types describe source | head -1`
stand mit `exit_code: 1` in der Trace, jetzt mit `exit_code: 0` und `stdout_truncated:
true`, ohne die Meldung des CPython-Flushs und mit Prozess-Exit 0 statt 120. Ein echter
Fehlschlag (`new entity --name ""`) steht weiterhin mit `exit_code: 1` und ohne das Flag.
- [x] **Das Verhaeltnis zum Hook-Issue ist dokumentiert.** In `EVALS.md` als Sachaussage ohne
Nummer, und als Kommentar an #82.
- [x] **`--minor` gebumpt, Changeset geschrieben, Vorbehalt geprueft** (siehe Entscheidung 5).
- [x] **`pytest`, `docs verify`, `instructions verify` ohne neue Befunde** - siehe § Verifiziert.
## Verifiziert
- `pytest`: 1309 Tests gruen (19 neu: 9 in `test_cli.py`, 5 in `test_run_budget.py`, 5 in
`test_telemetry_emit.py`).
- `docs verify`: OK - 56 Kommandos dokumentiert, keine Issue-Referenzen in 73 ausgelieferten
Dokumenten, TOCs aktuell, `CHANGES.md` nennt `6.1.0-beta.5`.
- `instructions verify`: OK - 23 Instruktionen, 7 Skills, 14 publizierte Kopien deckungsgleich.
- CI: Laeufe **303 und 304** zu `e4e2332` - beide `success`, inklusive Version-Gate und des
vollstaendigen `setup-instance.md`-Replays gegen einen frischen `dist export`. Lauf **305** zu
`4284f10` (nur `docs/`) lief zum Zeitpunkt des Schliessens noch.
- Live auf dieser Instanz gemessen, mit und ohne gesetzte Variable: `doctor`, `budget status`,
`budget.json`, Trace-Inhalt.
**Einschraenkung, die zum Nachweis oben gehoert:** das Trace-Verzeichnis dieser Sitzung enthaelt
nur noch die `wikitool`-Events. Die Umsetzungssitzung hat es bei einer Aufraeumaktion zwischen
zwei Pruefschritten selbst geloescht (`rm -rf reports/telemetry/$CLAUDE_CODE_SESSION_ID`) und damit
die beiden Hook-Events dieser Sitzung mitgenommen - gitignorierte, abgeleitete Daten, aber eben
auch der Beleg. Die Gleichheit der Schluessel ist trotzdem belegt: dieselbe Datei wurde zu Beginn
derselben Sitzung gelesen und trug dort `"session_id":"30d734d5-7f75-4890-b17e-1ce930f381fc"` in
`session.start` und `prompt.submitted` aus der Quelle `claude-code` - also genau den Wert, unter
dem die `wikitool`-Events jetzt stehen. Eine Trace, die beide Seiten in einem Schnappschuss zeigt,
entsteht bei der naechsten Prompt-Abgabe dieser oder jeder folgenden Sitzung von selbst.
## Bewusst nicht gemacht
- **`kb/concepts/workflows/Iteration and Cost Limits.md` nicht nachgezogen.** Die Seite nennt
weiter nur `WIKITOOL_SESSION_ID` und die PID als Fallback. Eine `kb/`-Seite braucht eine Quelle
(Invariante 3), und fuer diese Aenderung existiert keine in `raw/`; eine zu schreiben ist
Inhaltsarbeit nach den `kb/`-Regeln und nicht Teil dieses Werkpakets. Wer den naechsten
Stack-Lauf ohnehin als Quelle ablegt, zieht sie dabei mit.
- **Das Limit nicht angehoben** (Entscheidung 3).
- **Kein beobachtender Tool-Hook verdrahtet** - das ist #82 und bleibt dort; dieses Issue hat nur
die Reihenfolge geklaert.
## Herkunft
Befund 1 aus **#107** - dem getraceten Upgrade-Lauf 5.0.0 auf 6.0.0 auf einer echten
ausgelieferten Instanz, 2026-09-15, Harness Claude Code, Sitzung `671c1b9a`. Sechs der sieben
Befunde waren dort erledigt; dieser war der einzige, der eine Betreiberentscheidung brauchte.
Ausgelagert am 2026-09-16, entschieden und umgesetzt am selben Tag.
**Die Evidenz des urspruenglichen Befunds bleibt in #107**: die 63-Event-Trace verbatim im ersten
Kommentar, die Bucket-Messung und die Scorer-Ausgabe im Befund-1-Abschnitt, und der
Reproduktionsabschnitt, dessen Schritt 5 genau diesen Befund nachstellt - er reproduziert nach
`e4e2332` nicht mehr.
**Modelle dieser Sitzung** (kein mechanischer Check deckt eine dieser drei Phasen ab): Entwurf,
Versionsteil und Grenzurteil auf Opus 5; Code, Tests und Bump ebenfalls auf Opus 5 (der Wechsel
auf Sonnet wurde angeboten und nicht genommen); Abschlussphase auf Opus 5.
Changelog: Vorbereitungssitzung 2026-09-16 (Claude Code). Abschnitt "Zu entscheiden" ist durch "Entscheidungen" ersetzt - alle vier Achsen sind vom Betreiber entschieden: Fallback-Kette WIKITOOL_SESSION_ID → Registry belegter Harness-Variablen → getppid() (Wert unveraendert als Schluessel, kein Praefix); doctor wird dreiwertig statt eines zusaetzlichen Melde-/Gate-Punkts in publish; Limit bleibt 60; der SIGPIPE-Nebenbefund wird hier mitgemacht statt ausgelagert. Neu: eine Messung aus dieser Sitzung, die zeigt, dass CLAUDE_CODE_SESSION_ID ueber Bash-Calls hinweg stabil ist (die Shell-PID dagegen nicht) und exakt die Id ist, die der UserPromptSubmit-Hook in die Trace schreibt - darauf steht Entscheidung 1. Neu ausgearbeitet: "Zu bauen" (8 Punkte inkl. Tests und Doku-Pull-Through), "Zur Bauzeit zu pruefen" (Subagenten, Retention, Vorrang expliziter Ids) und der Versionsteil - --minor, gegen instructions/dev/version-parts.md als Drop-in in beide Richtungen geprueft, mit benanntem Vorbehalt. Akzeptanzkriterien praezisiert (Bucket-Schutz als Test statt als Argument ueber Schluesselraeume; SIGPIPE- und Meldungs-Kriterium ergaenzt). Labels: kind/decision → kind/build, size/L → size/M, weil die offenen Designfragen beantwortet sind und der Schnitt jetzt bei mehreren Dateien mit eigenem Testaufwand liegt.
**Changelog:** Vorbereitungssitzung 2026-09-16 (Claude Code). Abschnitt "Zu entscheiden" ist durch "Entscheidungen" ersetzt - alle vier Achsen sind vom Betreiber entschieden: Fallback-Kette `WIKITOOL_SESSION_ID` → Registry belegter Harness-Variablen → `getppid()` (Wert unveraendert als Schluessel, kein Praefix); `doctor` wird dreiwertig statt eines zusaetzlichen Melde-/Gate-Punkts in `publish`; Limit bleibt 60; der SIGPIPE-Nebenbefund wird hier mitgemacht statt ausgelagert. Neu: eine Messung aus dieser Sitzung, die zeigt, dass `CLAUDE_CODE_SESSION_ID` ueber Bash-Calls hinweg stabil ist (die Shell-PID dagegen nicht) und **exakt** die Id ist, die der `UserPromptSubmit`-Hook in die Trace schreibt - darauf steht Entscheidung 1. Neu ausgearbeitet: "Zu bauen" (8 Punkte inkl. Tests und Doku-Pull-Through), "Zur Bauzeit zu pruefen" (Subagenten, Retention, Vorrang expliziter Ids) und der Versionsteil - `--minor`, gegen `instructions/dev/version-parts.md` als Drop-in in beide Richtungen geprueft, mit benanntem Vorbehalt. Akzeptanzkriterien praezisiert (Bucket-Schutz als Test statt als Argument ueber Schluesselraeume; SIGPIPE- und Meldungs-Kriterium ergaenzt). Labels: `kind/decision` → `kind/build`, `size/L` → `size/M`, weil die offenen Designfragen beantwortet sind und der Schnitt jetzt bei mehreren Dateien mit eigenem Testaufwand liegt.
Changelog: Geschlossen. Koerper auf den Endzustand umgeschrieben: "Zu bauen" und "Zur Bauzeit zu pruefen" sind durch "Was gebaut wurde" ersetzt (alle acht Punkte umgesetzt), alle neun Akzeptanzkriterien abgehakt und je mit ihrem Beleg statt mit einer Zusage, "Verifiziert" nennt pytest/docs verify/instructions verify und die CI-Laeufe 303/304 (beide success). Neu im Koerper, weil es beim Bauen erst entstand: die SIGPIPE-Ursache ist gemessen (Click faengt EPIPE selbst ab und erzwingt sys.exit(1) - deshalb sitzt der Wrapper jetzt vor Click, nicht dahinter), und der Nachzug in docs/why-gates-are-code.md (4284f10) kam dazu, weil dieser Befund die Begruendung der Seite tatsaechlich bewegt: ein Gate in Code muss auch erreichbar sein, und eines, das nie ausloest, sieht aus wie eines, das niemand brauchte. Ebenfalls neu und bewusst so stehen gelassen: die Einschraenkung zum Bucket-Nachweis - die Umsetzungssitzung hat das Trace-Verzeichnis dieser Sitzung beim Aufraeumen selbst geloescht und damit die Hook-Events mitgenommen; was daraus noch belegbar ist und was nicht, steht im Koerper statt geglaettet zu werden. Abschnitt "Bewusst nicht gemacht" haelt drei Auslassungen fest (kb-Seite mangels Quelle, Limit unveraendert, Hook-Verdrahtung bleibt #82). Labels unveraendert area/process kind/build prio/planned size/M.
**Changelog:** Geschlossen. Koerper auf den Endzustand umgeschrieben: "Zu bauen" und "Zur Bauzeit zu pruefen" sind durch "Was gebaut wurde" ersetzt (alle acht Punkte umgesetzt), alle neun Akzeptanzkriterien abgehakt und je mit ihrem Beleg statt mit einer Zusage, "Verifiziert" nennt pytest/docs verify/instructions verify und die CI-Laeufe 303/304 (beide `success`). Neu im Koerper, weil es beim Bauen erst entstand: die SIGPIPE-Ursache ist gemessen (Click faengt EPIPE selbst ab und erzwingt `sys.exit(1)` - deshalb sitzt der Wrapper jetzt vor Click, nicht dahinter), und der Nachzug in `docs/why-gates-are-code.md` (`4284f10`) kam dazu, weil dieser Befund die Begruendung der Seite tatsaechlich bewegt: ein Gate in Code muss auch erreichbar sein, und eines, das nie ausloest, sieht aus wie eines, das niemand brauchte. Ebenfalls neu und bewusst so stehen gelassen: die Einschraenkung zum Bucket-Nachweis - die Umsetzungssitzung hat das Trace-Verzeichnis dieser Sitzung beim Aufraeumen selbst geloescht und damit die Hook-Events mitgenommen; was daraus noch belegbar ist und was nicht, steht im Koerper statt geglaettet zu werden. Abschnitt "Bewusst nicht gemacht" haelt drei Auslassungen fest (kb-Seite mangels Quelle, Limit unveraendert, Hook-Verdrahtung bleibt #82). Labels unveraendert `area/process kind/build prio/planned size/M`.
Nachtrag zu § Verifiziert: CI-Lauf 305 (4284f10, der docs/-Nachzug) ist inzwischen ebenfalls success. Damit sind alle drei Laeufe zu diesem Werkpaket gruen - 303 und 304 zu e4e2332, 305 zu 4284f10.
Nachtrag zu § Verifiziert: CI-Lauf **305** (`4284f10`, der `docs/`-Nachzug) ist inzwischen ebenfalls `success`. Damit sind alle drei Laeufe zu diesem Werkpaket gruen - 303 und 304 zu `e4e2332`, 305 zu `4284f10`.
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.
Ergebnis
Umgesetzt und publiziert am 2026-09-16,
6.1.0-beta.5(--minor,--impact high).e4e2332- die Aenderung selbst (15 Dateien, +610/-44), nach Freigabe des Mass-Update-Gates.4284f10- Nachzug indocs/why-gates-are-code.md(kein Bump:docs/liegt nicht imPfad-Scope des CI-Version-Gates).
Der Befund ist geschlossen: eine Sitzung auf Claude Code laeuft jetzt in einem Bucket, und das
Iteration-Budget-Gate zaehlt ueber einen ganzen Lauf. Der Beleg steht unter § Verifiziert.
Was der Befund war
chemenu.sessionfiel ohne gesetztesWIKITOOL_SESSION_IDaufos.getppid()zurueck. Auf ClaudeCode - jeder Bash-Tool-Call in einer frisch initialisierten Shell - war das eine neue "Sitzung" pro
Aufruf. Gemessen in #107: ein Lauf mit 33
wikitool-Aufrufen erschien als 21 Telemetrie-Buckets;der hoechste PID-Bucket kam auf 3 von 60 moeglichen Aufrufen. Zwei Folgen, beide behoben:
wikitool-Events diewechselnde PID.
EVALS.mds Satz "Everything joins onWIKITOOL_SESSION_ID" war auf demHauptharness falsch, und die L2-Regel
clearance-ended-the-turnmeldete "cannot say", obwohlder Harness
prompt.submittedfuenfmal gemeldet hatte.identische Aufrufe landeten in drei Buckets.
Entscheidungen (getroffen 2026-09-16, umgesetzt wie beschlossen)
WIKITOOL_SESSION_ID→ Registry belegter Harness-Variablen →getppid().Umgesetzt in
chemenu/session.pyalsHARNESS_ENV_VARSmit einem verifizierten Eintrag,CLAUDE_CODE_SESSION_ID→claude-code. Der Wert wird unveraendert Schluessel - keinPraefix, keine Normalisierung ausser dem bestehenden
session_slug()fuer Verzeichnisnamen,weil genau diese Gleichheit den Join mit den Hook-Events herstellt. Verworfen blieben: eine
Sitzungsdatei im Checkout und eine Branch/Worktree-Kette.
doctorist dreiwertig, die Herkunft steht in Trace und Status. Kein zusaetzlicherMeldepunkt in
publish- verworfen als Rauschen.Zahl kalibriert, fuer die es weiterhin keine Messung gibt. Die erste Sitzung, die es reisst,
ist die Messung - dann als eigenes Issue.
--minor. Gegeninstructions/dev/version-parts.mdgeprueft: vorwaerts einDrop-in (die Kette faellt weiterhin auf
getppid()zurueck,budget.jsonwird unveraendertweitergelesen, Eintraege ohne Herkunftsfeld behalten ihren Count), rueckwaerts ebenso (die alte
Fassung liest
count/recent/last_seenund ignoriert das zusaetzliche Feld). KeinKommando-, Flag- oder Variablen-Rename;
WIKITOOL_SESSION_IDbehaelt Namen und Vorrang. DerVorbehalt aus der Planung - Praefix oder Pflichtfeld waere ein Grenzuebertritt - ist geprueft
und traf nicht zu.
Was gebaut wurde
chemenu/session.py- die dreistufige Kette,session_id_source()nennt im Harness-Falldie Variable und den Harness (
CLAUDE_CODE_SESSION_ID (claude-code)). Der Modulkopf haeltfest, dass ein Registry-Eintrag nur nach Messung gegen eine echte Sitzung aufgenommen wird.
commands/run_budget.py-_entry_for(): jeder Eintrag traegt seine Herkunft; dieselbeId-Zeichenkette unter anderer Herkunft beginnt einen neuen Zaehler statt einen fremden zu
erben. Eintraege ohne das Feld (vor der Aenderung geschrieben) behalten ihren Count.
commands/doctor.py-check_session_id()dreiwertig:OKbei expliziter Variable,OKmit Nennung des Harness bei einer Registry-Variablen,
WARNnur noch beim PID-Fallback.telemetry/writer.py- dersession.start-Event derwikitool-Quelle traegtsession_origin. Nur diese Quelle, weil nur sie ihre Id ueber die Kette aufloest; jede anderebekommt die Id aus ihrer eigenen Hook-Payload gereicht.
cli.py(SIGPIPE) - gemessen, was die Planung offen gelassen hatte: Click faengtBrokenPipeErrorselbst ab (core.py,except OSError ... errno.EPIPE) und erzwingtsys.exit(1); ununterscheidbar von einem echten Fehlschlag, und deshalb nachtraeglich nichtmehr korrigierbar.
_BrokenPipeSwallowsitzt jetzt vor Click aufstdout/stderrundschluckt den EPIPE-Schreibfehler, bevor Click ihn sieht; der Aufruf endet mit seinem echten
Exit-Code und der Trace-Eintrag traegt
stdout_truncated: true._pacify_real_fd()haengt denechten Deskriptor danach an
/dev/null, sonst meldet CPythons eigener Flush beimInterpreter-Ende "Exception ignored while flushing sys.stdout" und der Prozess endet mit 120.
tests/test_cli.pyist neu (9 Tests);conftest.pys_WIKITOOL_ENVzieht dieRegistry-Variablen aus
HARNESS_ENV_VARS, weil diese Suite in Claude Code laeuft und sonstjeder Fallback-Test die echte Sitzungs-Id eingesammelt haette. Die beiden Gate-Tests laufen
ueber echte Subprozesse, weil
os.getppid()innerhalb eines Testprozesses konstant ist.EVALS.md(Join-Satz,session_id-Feldzeile, plus die Feststellung, dassein beobachtender Tool-Hook ohne gemeinsamen Schluessel nichts loest - ohne Issue-Nummer, das
Dokument wird ausgeliefert),
instructions/session-setup.md,INSTALL.md,tools/CONTRACT.md(
doctor-Zeile und Budget-Abschnitt),docs/why-gates-are-code.md.version bump --minor --impact highsamt Changeset-Prosa inCHANGES.md.Akzeptanzkriterien
Sitzung
30d734d5-7f75-4890-b17e-1ce930f381fc: dreiwikitool-Aufrufe aus dreiverschiedenen Shells (Shell-PIDs 4035563, 4035575 und die des Publish) stehen in
einem Bucket in
budget.json(count: 3,source: CLAUDE_CODE_SESSION_ID (claude-code)) und in einem Trace-Verzeichnis. Vor der Aenderung waeren das dreiBuckets mit je 1 gewesen. Siehe § Verifiziert fuer die Einschraenkung auf der Hook-Seite.
EVALS.mds Join-Satz ist ersetzt. Er beschreibt jetzt die Kette und die Bedingung,unter der ein Harness einen Registry-Eintrag bekommt; die
session_id-Feldzeile stimmt mitsession_id_source()ueberein.test_the_iteration_budget_gate_trips_across_separate_shellssetzt 61 Aufrufe aus je eigenem Subprozess ab, nur ueber die Harness-Variable - der 61. wird
verweigert.
test_the_loop_breaker_trips_across_separate_shellsanalog fuer drei identischeAufrufe. Beide Tests waeren vor dieser Aenderung gruen und blind gewesen.
test_run_budget.py: ein Bucket ohne Herkunftsfeld behaelt seinen Count (Altbestand), einermit abweichender Herkunft beginnt bei 0, einer mit gleicher Herkunft zaehlt weiter.
doctordreiwertig, Herkunft inbudget statusund imsession.start-Event. Livegeprueft: ohne
WIKITOOL_SESSION_IDmeldetdoctorOK session-id: scoped by harness variable CLAUDE_CODE_SESSION_ID (claude-code)undbudget statusnennt dieselbe Herkunft.Trace. Vorher/nachher am selben Kommando gemessen:
types describe source | head -1stand mit
exit_code: 1in der Trace, jetzt mitexit_code: 0undstdout_truncated: true, ohne die Meldung des CPython-Flushs und mit Prozess-Exit 0 statt 120. Ein echterFehlschlag (
new entity --name "") steht weiterhin mitexit_code: 1und ohne das Flag.EVALS.mdals Sachaussage ohneNummer, und als Kommentar an #82.
--minorgebumpt, Changeset geschrieben, Vorbehalt geprueft (siehe Entscheidung 5).pytest,docs verify,instructions verifyohne neue Befunde - siehe § Verifiziert.Verifiziert
pytest: 1309 Tests gruen (19 neu: 9 intest_cli.py, 5 intest_run_budget.py, 5 intest_telemetry_emit.py).docs verify: OK - 56 Kommandos dokumentiert, keine Issue-Referenzen in 73 ausgeliefertenDokumenten, TOCs aktuell,
CHANGES.mdnennt6.1.0-beta.5.instructions verify: OK - 23 Instruktionen, 7 Skills, 14 publizierte Kopien deckungsgleich.e4e2332- beidesuccess, inklusive Version-Gate und desvollstaendigen
setup-instance.md-Replays gegen einen frischendist export. Lauf 305 zu4284f10(nurdocs/) lief zum Zeitpunkt des Schliessens noch.doctor,budget status,budget.json, Trace-Inhalt.Einschraenkung, die zum Nachweis oben gehoert: das Trace-Verzeichnis dieser Sitzung enthaelt
nur noch die
wikitool-Events. Die Umsetzungssitzung hat es bei einer Aufraeumaktion zwischenzwei Pruefschritten selbst geloescht (
rm -rf reports/telemetry/$CLAUDE_CODE_SESSION_ID) und damitdie beiden Hook-Events dieser Sitzung mitgenommen - gitignorierte, abgeleitete Daten, aber eben
auch der Beleg. Die Gleichheit der Schluessel ist trotzdem belegt: dieselbe Datei wurde zu Beginn
derselben Sitzung gelesen und trug dort
"session_id":"30d734d5-7f75-4890-b17e-1ce930f381fc"insession.startundprompt.submittedaus der Quelleclaude-code- also genau den Wert, unterdem die
wikitool-Events jetzt stehen. Eine Trace, die beide Seiten in einem Schnappschuss zeigt,entsteht bei der naechsten Prompt-Abgabe dieser oder jeder folgenden Sitzung von selbst.
Bewusst nicht gemacht
kb/concepts/workflows/Iteration and Cost Limits.mdnicht nachgezogen. Die Seite nenntweiter nur
WIKITOOL_SESSION_IDund die PID als Fallback. Einekb/-Seite braucht eine Quelle(Invariante 3), und fuer diese Aenderung existiert keine in
raw/; eine zu schreiben istInhaltsarbeit nach den
kb/-Regeln und nicht Teil dieses Werkpakets. Wer den naechstenStack-Lauf ohnehin als Quelle ablegt, zieht sie dabei mit.
die Reihenfolge geklaert.
Herkunft
Befund 1 aus #107 - dem getraceten Upgrade-Lauf 5.0.0 auf 6.0.0 auf einer echten
ausgelieferten Instanz, 2026-09-15, Harness Claude Code, Sitzung
671c1b9a. Sechs der siebenBefunde waren dort erledigt; dieser war der einzige, der eine Betreiberentscheidung brauchte.
Ausgelagert am 2026-09-16, entschieden und umgesetzt am selben Tag.
Die Evidenz des urspruenglichen Befunds bleibt in #107: die 63-Event-Trace verbatim im ersten
Kommentar, die Bucket-Messung und die Scorer-Ausgabe im Befund-1-Abschnitt, und der
Reproduktionsabschnitt, dessen Schritt 5 genau diesen Befund nachstellt - er reproduziert nach
e4e2332nicht mehr.Modelle dieser Sitzung (kein mechanischer Check deckt eine dieser drei Phasen ab): Entwurf,
Versionsteil und Grenzurteil auf Opus 5; Code, Tests und Bump ebenfalls auf Opus 5 (der Wechsel
auf Sonnet wurde angeboten und nicht genommen); Abschlussphase auf Opus 5.
Changelog: Vorbereitungssitzung 2026-09-16 (Claude Code). Abschnitt "Zu entscheiden" ist durch "Entscheidungen" ersetzt - alle vier Achsen sind vom Betreiber entschieden: Fallback-Kette
WIKITOOL_SESSION_ID→ Registry belegter Harness-Variablen →getppid()(Wert unveraendert als Schluessel, kein Praefix);doctorwird dreiwertig statt eines zusaetzlichen Melde-/Gate-Punkts inpublish; Limit bleibt 60; der SIGPIPE-Nebenbefund wird hier mitgemacht statt ausgelagert. Neu: eine Messung aus dieser Sitzung, die zeigt, dassCLAUDE_CODE_SESSION_IDueber Bash-Calls hinweg stabil ist (die Shell-PID dagegen nicht) und exakt die Id ist, die derUserPromptSubmit-Hook in die Trace schreibt - darauf steht Entscheidung 1. Neu ausgearbeitet: "Zu bauen" (8 Punkte inkl. Tests und Doku-Pull-Through), "Zur Bauzeit zu pruefen" (Subagenten, Retention, Vorrang expliziter Ids) und der Versionsteil ---minor, gegeninstructions/dev/version-parts.mdals Drop-in in beide Richtungen geprueft, mit benanntem Vorbehalt. Akzeptanzkriterien praezisiert (Bucket-Schutz als Test statt als Argument ueber Schluesselraeume; SIGPIPE- und Meldungs-Kriterium ergaenzt). Labels:kind/decision→kind/build,size/L→size/M, weil die offenen Designfragen beantwortet sind und der Schnitt jetzt bei mehreren Dateien mit eigenem Testaufwand liegt.Changelog: Geschlossen. Koerper auf den Endzustand umgeschrieben: "Zu bauen" und "Zur Bauzeit zu pruefen" sind durch "Was gebaut wurde" ersetzt (alle acht Punkte umgesetzt), alle neun Akzeptanzkriterien abgehakt und je mit ihrem Beleg statt mit einer Zusage, "Verifiziert" nennt pytest/docs verify/instructions verify und die CI-Laeufe 303/304 (beide
success). Neu im Koerper, weil es beim Bauen erst entstand: die SIGPIPE-Ursache ist gemessen (Click faengt EPIPE selbst ab und erzwingtsys.exit(1)- deshalb sitzt der Wrapper jetzt vor Click, nicht dahinter), und der Nachzug indocs/why-gates-are-code.md(4284f10) kam dazu, weil dieser Befund die Begruendung der Seite tatsaechlich bewegt: ein Gate in Code muss auch erreichbar sein, und eines, das nie ausloest, sieht aus wie eines, das niemand brauchte. Ebenfalls neu und bewusst so stehen gelassen: die Einschraenkung zum Bucket-Nachweis - die Umsetzungssitzung hat das Trace-Verzeichnis dieser Sitzung beim Aufraeumen selbst geloescht und damit die Hook-Events mitgenommen; was daraus noch belegbar ist und was nicht, steht im Koerper statt geglaettet zu werden. Abschnitt "Bewusst nicht gemacht" haelt drei Auslassungen fest (kb-Seite mangels Quelle, Limit unveraendert, Hook-Verdrahtung bleibt #82). Labels unveraendertarea/process kind/build prio/planned size/M.Nachtrag zu § Verifiziert: CI-Lauf 305 (
4284f10, derdocs/-Nachzug) ist inzwischen ebenfallssuccess. Damit sind alle drei Laeufe zu diesem Werkpaket gruen - 303 und 304 zue4e2332, 305 zu4284f10.