fix: persist onboarding_completed for CLI-configured users on first chat_ready (#922)

* fix: persist onboarding_completed for CLI-configured users on first chat_ready (v0.50.179, #921)

Co-authored-by: bsgdigital

* fix(onboarding): don't 500 the status endpoint if save_settings fails

The #921 persist call `save_settings({"onboarding_completed": True})` in
get_onboarding_status() raises if the settings.json write fails
(read-only filesystem, disk full, permission error). That turns every
/api/onboarding/status call into a 500 until the disk is writable,
which is much worse UX than losing the persistence-across-restart guard.

Wrapped in try/except so persistence becomes best-effort. The function
still sets settings["onboarding_completed"] = True in memory on success,
and `completed` reflects `config_auto_completed` on this request either
way, so the user sees the right state even when the write fails — only
the next-restart protection degrades.

Added regression test that patches save_settings to raise OSError and
asserts the endpoint still returns completed=True without raising.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
nesquena-hermes
2026-04-23 15:46:02 -07:00
committed by GitHub
parent 1011918d50
commit a3647570fb
3 changed files with 93 additions and 14 deletions

View File

@@ -435,6 +435,25 @@ def get_onboarding_status() -> dict:
config_exists = Path(_get_config_path()).exists()
config_auto_completed = config_exists and bool(runtime.get("chat_ready"))
# Persist the flag so it survives future transient import failures (e.g. after
# a git branch switch in the hermes-agent repo). Without this, a CLI-configured
# user who never ran the wizard has no onboarding_completed flag — any momentary
# imports_ok=False during restart makes chat_ready=False, config_auto_completed=False,
# and the wizard reappears with a broken dropdown that clobbers their config.
#
# Best-effort: if save_settings raises (read-only FS, disk full, permission error),
# log and continue. The `config_auto_completed` branch of `completed=` below still
# returns True for this request, so the user sees the correct state — only the
# persistence-across-restart guarantee is degraded. Raising here would turn every
# /api/onboarding/status call into a 500 until disk was writable, which is worse UX
# than losing the next-restart protection.
if config_auto_completed and not settings.get("onboarding_completed"):
try:
save_settings({"onboarding_completed": True})
settings["onboarding_completed"] = True
except Exception:
logger.debug("Failed to persist onboarding_completed", exc_info=True)
return {
"completed": bool(settings.get("onboarding_completed")) or auto_completed or config_auto_completed,
"settings": {