Budget-Gate- und Loop-Breaker-Verweigerung druckt nach der ERROR-Zeile einen Python-Traceback #147

Closed
opened 2026-09-26 11:46:16 +00:00 by torben · 3 comments
Owner

Stand

Erledigt in 7.1.0-beta.27, Commit bc314e5 (2026-09-26). CI grün: Run 416 und Run 417.

Befund (behoben)

cli.main() rief run_budget.record_and_check() vor dem Typer-Dispatch auf. Verweigerte das Gate, rief es _util.fail(); das löste typer.Exit(1) aus. Außerhalb von app() fing diese Ausnahme niemand, also endete der Prozess mit korrektem Exit 1, aber mit einem Traceback nach der ERROR-Zeile:

$ WIKITOOL_SESSION_ID=<frisch> tools/wikitool touch __x__   # viermal
ERROR Loop-Breaker: the last 3 wikitool calls in this session were identical ...
Traceback (most recent call last):
  File ".../chemenu/cli.py", line 287, in main
    charged = run_budget.record_and_check(command, filtered[1:], override)
  File ".../chemenu/commands/run_budget.py", line 280, in record_and_check
    fail(loop_breaker_message(call_signature, loop_window))
  File ".../chemenu/commands/_util.py", line 53, in fail
    raise typer.Exit(code=1)
typer.exceptions.Exit

Der Traceback stand genau in der Ausgabe, die ein Agent laut AGENTS.md § Gates dem Nutzer zeigen und daraufhin anhalten soll. Er sah aus wie ein Absturz und lud zu dem Retry ein, den die Meldung verbietet (siehe #52). Zuerst beobachtet in #142 (Abschnitt „Außerhalb“).

Die älteren Subprozess-Tests (test_the_*_trips_across_separate_shells) konnten den Fehler nicht sehen: _spawn_call ruft record_and_check direkt auf, nicht cli.main().

Außerdem verwiesen vier Stellen auf Abschnitte, die es nicht gibt:

Stelle Nannte Nennt jetzt
run_budget.loop_breaker_message „the tooling contract's Tool Error Contracts“ AGENTS.md's Tool error contract
run_budget.call_limit_message „the tooling contract's 'Iteration and Cost Limits' section“ instructions/gates.md's 'Iteration Budget Gate and loop-breaker' section
run_budget.app-Hilfetext (wikitool budget -h) „the tooling contract's 'Iteration and Cost Limits'“ instructions/gates.md 'Iteration Budget Gate and loop-breaker'
Kommentar in cli.main() „the tooling contract's "Iteration and Cost Limits"“ wie oben

Der Docstring von run_budget.refund() nennt jetzt ebenfalls AGENTS.md's Tool error contract.

Entscheidungen

  1. Fix in cli.main(), eng um den Gate-Aufruf: record_and_check steht in try/except typer.Exit, danach folgt sys.exit(exc.exit_code). Einen generellen Handler um main() gibt es nicht, weil er echte Abstürze an anderer Stelle mit verschlucken würde.
    Verworfen: record_and_check einen Refusal-Wert statt fail() zurückgeben lassen. Das ändert eine Schnittstelle, die die In-Prozess-Tests über pytest.raises(typer.Exit) festhalten, und bringt nichts. Verworfen: die Gate-Prüfung nach _run_traced verlegen. Dann entstünde für Verweigerungen ein wikitool.call, die Telemetrie würde sich also ändern.
  2. Telemetrie unverändert: gate.refused wird weiter vor fail() emittiert. _run_traced läuft bei Verweigerungen weiterhin nicht, also entsteht kein wikitool.call und kein Refund.
  3. Stderr bleibt leer: _print_failure_hint findet ohne Click-Kontext nichts und gibt still auf (#143), wie beabsichtigt.
  4. Version: --patch, --impact medium, als Bump im Kandidaten 7.1.0-beta.27. Changelog-Text auf Englisch.

Akzeptanzkriterien

  • Eine Verweigerung durch Budget-Gate oder Loop-Breaker über den echten Einstiegspunkt endet mit Exit 1 und ohne Traceback, weder auf stdout noch auf stderr. Belegt durch test_loop_breaker_refusal_has_no_traceback und test_iteration_budget_refusal_has_no_traceback (Subprozess über python -m chemenu.cli).
  • Telemetrie unverändert: gate.refused wird mit dem passenden gate emittiert, für den verweigerten Aufruf entsteht kein wikitool.call, und der Budget-Zähler bleibt gleich (kein Eintrag, kein Refund). Geprüft in denselben beiden Tests. Zusätzlich hat eine manuelle Reproduktion gegen einen leeren CHEMENU_ROOT Trace und budget.json bestätigt.
  • Alle vier Stellen der Tabelle nennen einen existierenden Abschnitt. test_message_sections_exist_in_the_documents_they_cite prüft, dass die Überschriften ## Tool error contract (AGENTS.md) und ## Iteration Budget Gate and loop-breaker (instructions/gates.md) vorhanden sind.
  • pytest (1543 passed), docs verify und instructions verify waren vor dem Publish lokal grün. Der Changelog-Eintrag ist ein --patch-Bump. CI grün: Run 416 und Run 417.

Doku

Kein Pull-Through nötig. Kein Command-Record und keine Datei unter tools/CONTRACT.md zitiert die alten Meldungstexte. Der Satz „refuses with exit 1“ in instructions/gates.md und AGENTS.md § Gates bleibt wahr, ebenso die Zeile zu gate.refused in EVALS.md. Die Begründung in docs/why-gates-are-code.md ist nicht berührt.

Nicht in diesem Paket

Nachgezogen in b8ed8bd (7.1.0-beta.28): run_budget.py und instructions/gates.md nannten für Iteration and Cost Limits und Mass-Update Gate den Pfad kb/concepts/<Titel>.md. Beide Seiten liegen unter kb/concepts/workflows/ und werden jetzt über den Titel referenziert. Die Seite selbst verwies auf dieselben nicht existierenden AGENTS.md-Abschnitte wie die Meldungen hier; der Verweis ist korrigiert. Dass die Seite das Band 15–25 zitiert, ist gewollt: als belegte Branchen-Faustregel neben dem gemessenen Band. Folgebefund: #150 (Pfadangaben im new-Record).

Beim Publish fehlten dem Commit bc314e5 die Attributionszeilen (Co-Authored-By/Claude-Session). Er war zu diesem Zeitpunkt schon gepusht. Nachträglich ändern ließe er sich nur per Force-Push, und den verbietet Invariante 5.

Dateien

tools/chemenu/cli.py (main), tools/chemenu/commands/run_budget.py (app, loop_breaker_message, call_limit_message, refund), tools/chemenu/tests/test_run_budget.py, CHANGES.md, VERSION.

Modelle

Design, Versionsteil und Grenzentscheidung (stack-dev Schritt 3): Opus 5.5. Mechanischer Teil (Code, Tests, Bump, Changelog-Text, Publish): Sonnet 5. Abschluss (stack-close): Opus 5.5.

Verwandt: #143, #52, #142.

## Stand **Erledigt** in `7.1.0-beta.27`, Commit `bc314e5` (2026-09-26). CI grün: [Run 416](https://gitea.nehmer.net/torben/chemenu/actions/runs/416) und [Run 417](https://gitea.nehmer.net/torben/chemenu/actions/runs/417). ## Befund (behoben) `cli.main()` rief `run_budget.record_and_check()` vor dem Typer-Dispatch auf. Verweigerte das Gate, rief es `_util.fail()`; das löste `typer.Exit(1)` aus. Außerhalb von `app()` fing diese Ausnahme niemand, also endete der Prozess mit korrektem Exit 1, aber mit einem Traceback nach der `ERROR`-Zeile: ``` $ WIKITOOL_SESSION_ID=<frisch> tools/wikitool touch __x__ # viermal ERROR Loop-Breaker: the last 3 wikitool calls in this session were identical ... Traceback (most recent call last): File ".../chemenu/cli.py", line 287, in main charged = run_budget.record_and_check(command, filtered[1:], override) File ".../chemenu/commands/run_budget.py", line 280, in record_and_check fail(loop_breaker_message(call_signature, loop_window)) File ".../chemenu/commands/_util.py", line 53, in fail raise typer.Exit(code=1) typer.exceptions.Exit ``` Der Traceback stand genau in der Ausgabe, die ein Agent laut AGENTS.md § Gates dem Nutzer zeigen und daraufhin anhalten soll. Er sah aus wie ein Absturz und lud zu dem Retry ein, den die Meldung verbietet (siehe #52). Zuerst beobachtet in #142 (Abschnitt „Außerhalb“). Die älteren Subprozess-Tests (`test_the_*_trips_across_separate_shells`) konnten den Fehler nicht sehen: `_spawn_call` ruft `record_and_check` direkt auf, nicht `cli.main()`. Außerdem verwiesen vier Stellen auf Abschnitte, die es nicht gibt: | Stelle | Nannte | Nennt jetzt | |---|---|---| | `run_budget.loop_breaker_message` | „the tooling contract's Tool Error Contracts“ | AGENTS.md's Tool error contract | | `run_budget.call_limit_message` | „the tooling contract's 'Iteration and Cost Limits' section“ | `instructions/gates.md`'s 'Iteration Budget Gate and loop-breaker' section | | `run_budget.app`-Hilfetext (`wikitool budget -h`) | „the tooling contract's 'Iteration and Cost Limits'“ | `instructions/gates.md` 'Iteration Budget Gate and loop-breaker' | | Kommentar in `cli.main()` | „the tooling contract's "Iteration and Cost Limits"“ | wie oben | Der Docstring von `run_budget.refund()` nennt jetzt ebenfalls AGENTS.md's Tool error contract. ## Entscheidungen 1. **Fix in `cli.main()`, eng um den Gate-Aufruf:** `record_and_check` steht in `try/except typer.Exit`, danach folgt `sys.exit(exc.exit_code)`. Einen generellen Handler um `main()` gibt es nicht, weil er echte Abstürze an anderer Stelle mit verschlucken würde. *Verworfen:* `record_and_check` einen Refusal-Wert statt `fail()` zurückgeben lassen. Das ändert eine Schnittstelle, die die In-Prozess-Tests über `pytest.raises(typer.Exit)` festhalten, und bringt nichts. *Verworfen:* die Gate-Prüfung nach `_run_traced` verlegen. Dann entstünde für Verweigerungen ein `wikitool.call`, die Telemetrie würde sich also ändern. 2. **Telemetrie unverändert:** `gate.refused` wird weiter vor `fail()` emittiert. `_run_traced` läuft bei Verweigerungen weiterhin nicht, also entsteht kein `wikitool.call` und kein Refund. 3. **Stderr bleibt leer:** `_print_failure_hint` findet ohne Click-Kontext nichts und gibt still auf (#143), wie beabsichtigt. 4. **Version:** `--patch`, `--impact medium`, als Bump im Kandidaten `7.1.0-beta.27`. Changelog-Text auf Englisch. ## Akzeptanzkriterien - [x] Eine Verweigerung durch Budget-Gate oder Loop-Breaker über den echten Einstiegspunkt endet mit Exit 1 und ohne Traceback, weder auf stdout noch auf stderr. Belegt durch `test_loop_breaker_refusal_has_no_traceback` und `test_iteration_budget_refusal_has_no_traceback` (Subprozess über `python -m chemenu.cli`). - [x] Telemetrie unverändert: `gate.refused` wird mit dem passenden `gate` emittiert, für den verweigerten Aufruf entsteht kein `wikitool.call`, und der Budget-Zähler bleibt gleich (kein Eintrag, kein Refund). Geprüft in denselben beiden Tests. Zusätzlich hat eine manuelle Reproduktion gegen einen leeren `CHEMENU_ROOT` Trace und `budget.json` bestätigt. - [x] Alle vier Stellen der Tabelle nennen einen existierenden Abschnitt. `test_message_sections_exist_in_the_documents_they_cite` prüft, dass die Überschriften `## Tool error contract` (AGENTS.md) und `## Iteration Budget Gate and loop-breaker` (`instructions/gates.md`) vorhanden sind. - [x] `pytest` (1543 passed), `docs verify` und `instructions verify` waren vor dem Publish lokal grün. Der Changelog-Eintrag ist ein `--patch`-Bump. CI grün: [Run 416](https://gitea.nehmer.net/torben/chemenu/actions/runs/416) und [Run 417](https://gitea.nehmer.net/torben/chemenu/actions/runs/417). ## Doku Kein Pull-Through nötig. Kein Command-Record und keine Datei unter `tools/CONTRACT.md` zitiert die alten Meldungstexte. Der Satz „refuses with exit 1“ in `instructions/gates.md` und AGENTS.md § Gates bleibt wahr, ebenso die Zeile zu `gate.refused` in `EVALS.md`. Die Begründung in `docs/why-gates-are-code.md` ist nicht berührt. ## Nicht in diesem Paket Nachgezogen in `b8ed8bd` (`7.1.0-beta.28`): `run_budget.py` und `instructions/gates.md` nannten für `Iteration and Cost Limits` und `Mass-Update Gate` den Pfad `kb/concepts/<Titel>.md`. Beide Seiten liegen unter `kb/concepts/workflows/` und werden jetzt über den Titel referenziert. Die Seite selbst verwies auf dieselben nicht existierenden AGENTS.md-Abschnitte wie die Meldungen hier; der Verweis ist korrigiert. Dass die Seite das Band 15–25 zitiert, ist gewollt: als belegte Branchen-Faustregel neben dem gemessenen Band. Folgebefund: #150 (Pfadangaben im `new`-Record). Beim Publish fehlten dem Commit `bc314e5` die Attributionszeilen (`Co-Authored-By`/`Claude-Session`). Er war zu diesem Zeitpunkt schon gepusht. Nachträglich ändern ließe er sich nur per Force-Push, und den verbietet Invariante 5. ## Dateien `tools/chemenu/cli.py` (`main`), `tools/chemenu/commands/run_budget.py` (`app`, `loop_breaker_message`, `call_limit_message`, `refund`), `tools/chemenu/tests/test_run_budget.py`, `CHANGES.md`, `VERSION`. ## Modelle Design, Versionsteil und Grenzentscheidung (stack-dev Schritt 3): Opus 5.5. Mechanischer Teil (Code, Tests, Bump, Changelog-Text, Publish): Sonnet 5. Abschluss (stack-close): Opus 5.5. Verwandt: #143, #52, #142.
torben added the prio/plannedsize/Sarea/processkind/defect labels 2026-09-26 11:46:16 +00:00
Author
Owner

Changelog: Befund am Baum reproduziert, Zeilennummern aktualisiert. Neu: Abschnitte Stand, Entscheidungen (Fix eng in cli.main(), Telemetrie bleibt unverändert, --patch/medium) und Umsetzungsplan. Bei den veralteten Verweisen kamen zwei Stellen dazu, der budget-Hilfetext und der Kommentar in main(). Kriterium 2 wird jetzt als prüfbare Eigenschaft formuliert (kein wikitool.call, Zähler unverändert), Kriterium 3 bekommt einen Überschriften-Existenztest.

**Changelog:** Befund am Baum reproduziert, Zeilennummern aktualisiert. Neu: Abschnitte Stand, Entscheidungen (Fix eng in `cli.main()`, Telemetrie bleibt unverändert, `--patch`/`medium`) und Umsetzungsplan. Bei den veralteten Verweisen kamen zwei Stellen dazu, der `budget`-Hilfetext und der Kommentar in `main()`. Kriterium 2 wird jetzt als prüfbare Eigenschaft formuliert (kein `wikitool.call`, Zähler unverändert), Kriterium 3 bekommt einen Überschriften-Existenztest.
Author
Owner

Changelog: Abgeschlossen. Der Body beschreibt jetzt den Endstand: Befund in der Vergangenheit, alle vier Kriterien abgehakt und mit Beleg (Tests, CI 416/417 grün). Neu sind die Abschnitte Doku (kein Pull-Through nötig), Nicht in diesem Paket (Korpus-Kommentar, fehlende Attribution an bc314e5) und Modelle.

**Changelog:** Abgeschlossen. Der Body beschreibt jetzt den Endstand: Befund in der Vergangenheit, alle vier Kriterien abgehakt und mit Beleg (Tests, CI 416/417 grün). Neu sind die Abschnitte Doku (kein Pull-Through nötig), Nicht in diesem Paket (Korpus-Kommentar, fehlende Attribution an `bc314e5`) und Modelle.
Author
Owner

Nachtrag (b8ed8bd, 7.1.0-beta.28): Der im Body unter „Nicht in diesem Paket“ genannte Punkt ist behoben. Die Formulierung dort war allerdings ungenau: Die Seite Iteration and Cost Limits zitiert das Band 15–25 absichtlich als belegte Branchen-Faustregel, direkt neben dem gemessenen Band, und daran war nichts falsch. Veraltet waren drei andere Dinge:

  • run_budget.py und instructions/gates.md nannten den nicht mehr existierenden Pfad kb/concepts/<Titel>.md. In gates.md betraf das auch das Mass-Update Gate. Beide Seiten liegen unter kb/concepts/workflows/ und werden jetzt über den Titel referenziert.
  • Die Seite verwies auf die nicht existierenden AGENTS.md-Abschnitte „Tool Error Contracts“ und „Iteration and Cost Limits“, also denselben Defekt wie hier. Der Verweis zeigt jetzt auf AGENTS.md § Tool error contract und instructions/gates.md § Iteration Budget Gate and loop-breaker.

Der Abschnitt „Nicht in diesem Paket“ im Body ist entsprechend korrigiert. Folgebefund: #150 (die Notizen im new-Record nennen für concept und source einen flachen Zielpfad).

**Nachtrag (`b8ed8bd`, `7.1.0-beta.28`):** Der im Body unter „Nicht in diesem Paket“ genannte Punkt ist behoben. Die Formulierung dort war allerdings ungenau: Die Seite `Iteration and Cost Limits` zitiert das Band 15–25 **absichtlich** als belegte Branchen-Faustregel, direkt neben dem gemessenen Band, und daran war nichts falsch. Veraltet waren drei andere Dinge: - `run_budget.py` und `instructions/gates.md` nannten den nicht mehr existierenden Pfad `kb/concepts/<Titel>.md`. In `gates.md` betraf das auch das `Mass-Update Gate`. Beide Seiten liegen unter `kb/concepts/workflows/` und werden jetzt über den Titel referenziert. - Die Seite verwies auf die nicht existierenden AGENTS.md-Abschnitte „Tool Error Contracts“ und „Iteration and Cost Limits“, also denselben Defekt wie hier. Der Verweis zeigt jetzt auf AGENTS.md § Tool error contract und `instructions/gates.md` § Iteration Budget Gate and loop-breaker. Der Abschnitt „Nicht in diesem Paket“ im Body ist entsprechend korrigiert. Folgebefund: #150 (die Notizen im `new`-Record nennen für `concept` und `source` einen flachen Zielpfad).
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: torben/chemenu#147