From 25625677305f756c73d24189d832a2f8b77171bf Mon Sep 17 00:00:00 2001 From: Nathan Esquenazi Date: Sun, 12 Apr 2026 13:22:48 -0700 Subject: [PATCH] fix: onboarding completes gracefully for pre-configured providers (closes #322) (#323) OAuth/CLI-configured providers (openai-codex, copilot, nous) no longer blocked by onboarding wizard. 5 new tests, 758 total. --- CHANGELOG.md | 5 ++++ api/onboarding.py | 6 ++++- static/index.html | 2 +- tests/test_sprint34.py | 56 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78bb11f..7d71c95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ --- +## [v0.50.3] Onboarding completes gracefully for pre-configured providers (PR #323, fixes #322) + +- **OAuth/CLI-configured providers no longer blocked by onboarding** (closes #322): Users with providers already set up via the CLI (`openai-codex`, `copilot`, `nous`, etc.) hit `Unsupported provider for WebUI onboarding` when clicking "Open Hermes" on the finish page. The wizard now marks onboarding complete and lets them through — the agent setup is already done, no wizard steps needed. + - 5 new tests in `tests/test_sprint34.py`; 758 tests total (up from 753) + ## [v0.50.2] Workspace panel state persists across refreshes - **Workspace panel open/closed persists** (localStorage key `hermes-webui-workspace-panel`): Once you open the workspace/files pane, it stays open after a page refresh. Closing it explicitly saves the closed state, which also survives a refresh. The restore happens in the boot sequence before the first render, so there is no flash of the wrong state. Works for both desktop and mobile. diff --git a/api/onboarding.py b/api/onboarding.py index 3139b1a..be9ddf7 100644 --- a/api/onboarding.py +++ b/api/onboarding.py @@ -417,7 +417,11 @@ def apply_onboarding_setup(body: dict) -> dict: base_url = _normalize_base_url(str(body.get("base_url") or "")) if provider not in _SUPPORTED_PROVIDER_SETUPS: - raise ValueError("Unsupported provider for WebUI onboarding.") + # Unsupported providers (openai-codex, copilot, nous, etc.) are already + # configured via the CLI. Just mark onboarding as complete and let the + # user through — the agent is already set up, no further setup needed. + save_settings({"onboarding_completed": True}) + return get_onboarding_status() if not model: raise ValueError("model is required") diff --git a/static/index.html b/static/index.html index 4b1b941..dec6669 100644 --- a/static/index.html +++ b/static/index.html @@ -526,7 +526,7 @@
System
Instance version and access controls.
- v0.50.2 + v0.50.3
diff --git a/tests/test_sprint34.py b/tests/test_sprint34.py index f0a39b5..4001ff5 100644 --- a/tests/test_sprint34.py +++ b/tests/test_sprint34.py @@ -242,3 +242,59 @@ def test_control_center_tab_highlight_on_open(): """Opening the control center must use settings-tabs for section navigation.""" css = open('static/style.css').read() assert 'settings-tabs' in css, 'settings-tabs CSS class for control center tabs missing from style.css' + + +# ── apply_onboarding_setup: unsupported/OAuth providers complete gracefully ── + +class TestApplyOnboardingSetupUnsupportedProvider: + """PR #323 / Issue #322: apply_onboarding_setup must not raise ValueError for + providers already configured via CLI (openai-codex, copilot, nous, etc.). + Instead it marks onboarding complete and returns current status. + """ + + def _call(self, provider: str) -> dict: + import sys, pathlib, unittest.mock, tempfile, os + repo = pathlib.Path(__file__).parent.parent + if str(repo) not in sys.path: + sys.path.insert(0, str(repo)) + + from api.onboarding import apply_onboarding_setup + + with tempfile.TemporaryDirectory() as tmp: + with unittest.mock.patch("api.onboarding._get_active_hermes_home", + return_value=pathlib.Path(tmp)), \ + unittest.mock.patch("api.onboarding._get_config_path", + return_value=pathlib.Path(tmp) / "config.yaml"), \ + unittest.mock.patch("api.onboarding.save_settings") as mock_save, \ + unittest.mock.patch("api.onboarding.get_onboarding_status", + return_value={"completed": True, "system": {}}): + result = apply_onboarding_setup({"provider": provider, "model": "", "api_key": ""}) + return result, mock_save + + def test_openai_codex_does_not_raise(self): + """apply_onboarding_setup with openai-codex must not raise ValueError.""" + result, _ = self._call("openai-codex") + assert result is not None + + def test_copilot_does_not_raise(self): + """apply_onboarding_setup with copilot must not raise ValueError.""" + result, _ = self._call("copilot") + assert result is not None + + def test_nous_does_not_raise(self): + """apply_onboarding_setup with nous must not raise ValueError.""" + result, _ = self._call("nous") + assert result is not None + + def test_unsupported_provider_marks_onboarding_complete(self): + """apply_onboarding_setup with an unsupported provider must save onboarding_completed=True.""" + _, mock_save = self._call("openai-codex") + calls = [str(c) for c in mock_save.call_args_list] + assert any("onboarding_completed" in c for c in calls), \ + "save_settings must be called with onboarding_completed=True for unsupported providers" + + def test_unsupported_provider_returns_status_dict(self): + """apply_onboarding_setup with an unsupported provider must return a status dict (not raise).""" + result, _ = self._call("openai-codex") + assert isinstance(result, dict), \ + "apply_onboarding_setup must return a dict for unsupported providers, not raise"