Skip to content

fix(fallback): restore the primary model/auth route after any picker failure in hermes fallback add - #104209

Merged
teknium1 merged 2 commits into
mainfrom
fix/devchan-c085-picker-notices-fallback
Sep 6, 2026
Merged

teknium1 merged 2 commits into
mainfrom
fix/devchan-c085-picker-notices-fallback

Conversation

@teknium1

@teknium1 teknium1 commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator

hermes fallback add now restores the primary model/auth route after any picker failure (ordinary exception, Ctrl+C, post-picker config read error) instead of leaving the temporarily selected fallback active as the primary.

Root cause

hermes_cli/fallback_cmd.py::cmd_fallback_add reuses the canonical hermes model picker, which persists its selection as the primary model in config.yaml (and touches auth.json active_provider); the command then snapshots/restores around it. On main the restore ran only on SystemExit and on the normal-return paths. An ordinary exception from inside the picker (e.g. an OSError from deactivate_provider after the model was already written) propagated straight out, and the user's primary was silently replaced by the fallback they were trying to add. Cleanup was also best-effort (except Exception: pass) in the auth half, and an absent active_provider key was written back as an explicit null.

Fix (contributor commit, cherry-picked verbatim)

  • One try/except BaseException boundary around the picker and the post-picker config read; restore both stores, re-raise the original error with the restore outcome attached as a note (add_note) instead of masking it.
  • Successful path restores exactly once, before identity/dedup/append work can fail.
  • _restore_primary_route attempts the config half and the auth half independently and reports partial failure (RuntimeError) rather than swallowing it.
  • Auth snapshot/restore happen under _auth_store_lock(); a missing active_provider stays missing (sentinel) instead of becoming null.
  • Picker-created custom_providers / credentials are retained; only model and active_provider are rolled back.

Before / after (real CLI under a Linux PTY)

Harness: evals/cli_fallback_add_picker_error.py --root <checkout> --output <dir> --expect stranded|restored. Temp HERMES_HOME, primary openrouter / primary/model-a, a saved custom provider with discover_models: false (no network). After the provider menu renders (snapshot already taken) the harness chmod 0s auth.json, then picks the custom provider + its model: the canonical picker writes the temporary primary and then raises a plain PermissionError from deactivate_provider.

base 3513a3b9227 this branch
picker error surfaced yes (PermissionError, exit 1) yes (same error, exit 1)
config.yaml model afterwards {provider: custom, default: lab-model, base_url: http://127.0.0.1:9/v1} — primary replaced {provider: openrouter, default: primary/model-a, …} — primary restored
restore-failure reported n/a (no restore ran) note appended: Could not fully restore the primary route … auth.json (auth half legitimately unreadable in this scenario; config half restored)

Raw PTY captures kept locally (not published).

Tests (tests/hermes_cli/test_fallback_cmd.py, 2 invariants)

  • test_picker_failure_restores_persisted_primary_without_masking_error[exception|ctrl-c] — a picker that rewrites model then raises LookupError / KeyboardInterrupt: the same exception object propagates and the persisted model equals the pre-picker primary. Red on base (assert persisted["model"] == primary_model), green here.
  • test_restore_preserves_absent_active_provider — snapshot+restore of a store without active_provider leaves the key absent. Red on base, green here.

The salvaged suite's mock-dispatch tests (asserting calls into our own helpers) were dropped in the follow-up commit; the existing side-effect-retention assertion (custom_providers survives) is kept.

scripts/run_tests.sh -j 1 tests/hermes_cli/test_fallback_cmd.py tests/hermes_cli/test_fallback_config.py → 22 passed. ruff, check-windows-footguns.py --all, check_compat_pointers.py, git diff --check clean.

Limitations

  • Linux PTY harness only; numbered-menu path (curses stubbed) — the curses UI itself is not what changed.
  • The hermes subagent picker (feat(cli): add subagent model and reasoning picker #76480) shares this route-isolation concern; it is reviewed separately and does not depend on this PR.

Credit / originals

Salvages #76497 by @Xipong (authorship preserved via cherry-pick; noreply email auto-maps). Fixes the contract in #85651. The sweeper review's one ask (a persisted-config failure-path test) was already addressed by the author in 4944099; this PR keeps that test and parametrizes it.

Campaign tracker: #104154

Infographic

fallback-add-route-restore

@github-actions

github-actions Bot commented Sep 6, 2026 •

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

ran on f39966a — test(fallback): keep the two persisted-config invariants; PT

⚠️ Warnings

OSV vulnerability scan · View job

28 known vulnerabilities found in pinned dependencies.

How to fix:

Review the findings in the Security tab. Update the affected dependencies if a patched version is available.


debug info

CI timings

CI timings · View report · View job

Wall time 4m36s vs 4m31s (+1.8%). 6 job(s) slower, 8 faster,

  • Python tests / Run tests: -21.0s
  • OS-specific tests / macOS-only tests: +17.0s
  • OSV scan / Scan lockfiles / osv-scan: -8.0s
  • Python lints / Windows footguns (blocking): +6.0s
  • OS-specific tests / Windows-only tests: +4.0s

Xipong and others added 2 commits September 6, 2026 03:29
… for the picker error path

Trim the salvaged suite to the behaviour contracts: a picker exception or Ctrl+C leaves
config.yaml's model exactly as snapshotted (parametrized), and an absent active_provider
stays absent through snapshot+restore. The mock-dispatch tests (asserting calls into our own
helpers) are dropped. evals/cli_fallback_add_picker_error.py drives the real
`hermes fallback add` under a Linux PTY into an ordinary picker OSError and checks the
persisted model: stranded on base, restored after.
@teknium1
teknium1 force-pushed the fix/devchan-c085-picker-notices-fallback branch from 57f2d18 to f39966a Compare September 6, 2026 10:29
@alt-glitch alt-glitch added type/bug Something isn't working comp/cli CLI entry point, hermes_cli/, setup wizard area/config Config system, migrations, profiles area/auth Authentication, OAuth, credential pools P2 Medium — degraded but workaround exists labels Sep 6, 2026
@teknium1
teknium1 merged commit 91b4d9f into main Sep 6, 2026
40 checks passed
@teknium1
teknium1 deleted the fix/devchan-c085-picker-notices-fallback branch September 6, 2026 12:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/auth Authentication, OAuth, credential pools area/config Config system, migrations, profiles comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants