Skip to content

fix(ui): make first-run onboarding recoverable instead of looping on a finish error - #11884

Closed
roninjin10 wants to merge 1 commit into
elizaOS:developfrom
roninjin10:fix/first-run-loop-configure-settings
Closed

fix(ui): make first-run onboarding recoverable instead of looping on a finish error#11884
roninjin10 wants to merge 1 commit into
elizaOS:developfrom
roninjin10:fix/first-run-loop-configure-settings

Conversation

@roninjin10

Copy link
Copy Markdown
Contributor

Closes #11882

The bug

The in-chat first-run wizard could loop forever with no escape, so a fresh install can never finish configuring a model provider:

Hi — I'm Eliza. Let's get you set up. First, where should your agent run?
  [On this device]
Which model provider should Eliza use?
  [On this device (recommended)]
Starting local agent
Saving first-run profile
Not found            <-- HTTP 404 from POST /api/first-run
Hi — I'm Eliza. Let's get you set up. First, where should your agent run?   <-- loops back forever

Selecting "Other / configure in Settings" did not escape either — it re-ran the same finish flow, hit the same failure, and re-looped.

Root cause (UX/navigation)

In packages/ui/src/first-run/use-first-run-conductor.ts:

  1. On any finish/provision error, seedError(message) seeded a turn as `${message}\n\n${RUNTIME_CHOICE}` — it re-appended the runtime question. A persistent finish error (the 404 from POST /api/first-run) therefore re-offered the same runtime choice indefinitely, with no distinct error surface, no explanation, and no way out.
  2. provider:other ("Other / configure in Settings") ran a local finish (configure-later) that, on the same 404, also fell into seedError and re-looped instead of ever opening Settings.

Scope note — the 404 is NOT fixed here (it's a separate issue)

The underlying POST /api/first-run "Not found" 404 is a backend/dev-environment problem (the local app-core/agent server not serving /api/first-run) and requires a running environment to diagnose. This PR does not touch it. It fixes the UX/navigation defect so onboarding is recoverable — retry + an explicit escape into Settings — even when finish keeps failing.

The fix

  • Distinct, non-looping error surface. Finish errors now seed a dedicated recovery turn (first-run:error:*) with a human-readable message and its own [CHOICE:first-run id=error]:
    • Try again (error:retry) — re-runs the last runtime's finish (local re-POSTs; cloud re-provisions, re-seeding the OAuth turn).
    • Choose a different way to run (error:restart) — re-offers a fresh, unlocked runtime CHOICE so the user can switch cloud↔local.
    • Configure in Settings (error:settings) — the guaranteed escape.
      The runtime question is never re-appended inline again, so a repeating error can't loop the greeting forever.
  • "Other / configure in Settings" now escapes. provider:other opens the Settings view via setTab("settings") and exits first-run via completeFirstRun("settings") (route id "settings", the same id used everywhere in App.tsx and the existing needs-provider banner in first-run-finish.ts). It no longer runs a finish flow that could fail and trap the user.
  • Both escapes route through one exitToSettings helper, latched by completedRef so a double-tap can't flip the gate twice.
  • Terse transport strings ("Not found", "Failed to fetch", …) are wrapped in a clear human sentence by finishErrorMessage.

Local-success, cloud, and needs-cloud-login paths are unchanged.

Before / after behavior

Before After
Persistent finish 404 Re-appends the runtime question → infinite loop, no escape Distinct error turn with a clear message + Try again / Choose a different way to run / Configure in Settings
"Other / configure in Settings" Runs a local finish → same 404 → re-loops Opens Settings (setTab("settings")) and exits first-run
Cloud listing failure Error turn re-offered the runtime CHOICE Error turn offers retry/restart/Settings; error:retry re-runs cloud provisioning

Tests (primary evidence — the conductor is headless)

bun run --cwd packages/ui test src/first-run/191 passed (15 files), including:

  • New: a persistent POST /api/first-run 404 does not re-loop the runtime question — the error turn is distinct (human message, no runtime:local button), error:retry re-attempts (2× POST, one bounded error turn per attempt), and error:settings calls setTab("settings") + completeFirstRun("settings") to escape.
  • New: error:retry after a cloud listing failure re-seeds the OAuth turn and re-runs provisioning (0 agents → auto-provision → tutorial).
  • Updated: provider:other opens the Settings tab and exits first-run without any finish flow / POST / model download / tutorial, and a double-tap flips the gate exactly once.
  • Updated: the cloud-failure recovery and the "fresh provider turn on runtime re-pick" tests now drive the new error:restart → runtime re-pick path.
  • Hardened: beforeEach restores default mock implementations (clearAllMocks keeps implementations, so a leaked mockRejectedValue would poison later tests).
  • The confused-user fuzz storms (use-first-run-conductor.fuzz.test.ts) still hold all invariants (≤1 POST, ≤1 provision, ≤1 completeFirstRun, bounded transcript).

Verification run

  • bun run --cwd packages/ui typecheck — clean.
  • bunx @biomejs/biome check on changed files — clean.
  • bun run --cwd packages/ui test src/first-run/ — 191 passed.

Evidence checklist

  • Real-LLM trajectories — N/A: no agent/action/provider/prompt/model behavior changed; this is chat-transcript navigation in a headless conductor.
  • Unit/behavior tests — attached above (the flow is a headless conductor; these are the real evidence).
  • Rendered UI proof (screenshots/video) — N/A here: reproducing the live loop needs the backend /api/first-run 404 (a separate, out-of-scope environment issue). The conductor logic is fully covered by the real finish use case under test.
  • Backend logs — N/A: no server code changed.

🤖 Generated with Claude Code

…a finish error

The in-chat first-run wizard could loop forever with no escape. On any
finish/provision error, `seedError` re-appended the runtime CHOICE
(`${message}\n\n${RUNTIME_CHOICE}`), so a persistent failure — e.g. the
"Not found" 404 from `POST /api/first-run` — re-offered the same runtime
question indefinitely with no distinct error surface and no way out. The
"Other / configure in Settings" provider pick made it worse: it ran a local
finish that hit the same 404 and re-looped instead of ever opening Settings.

This is the UX/navigation fix (the underlying 404 is a separate backend/env
issue, out of scope):

- Finish errors now seed a DISTINCT, non-looping recovery turn
  (`first-run:error:*`) with a human message and a dedicated
  `[CHOICE:first-run id=error]`: Try again (`error:retry`, re-runs the last
  runtime's finish), Choose a different way to run (`error:restart`, re-offers a
  fresh unlocked runtime CHOICE), and Configure in Settings (`error:settings`).
- "Other / configure in Settings" (`provider:other`) now opens the Settings tab
  (`setTab("settings")`) and exits first-run (`completeFirstRun("settings")`)
  via a shared `exitToSettings` helper, latched by `completedRef` so a
  double-tap can't flip the gate twice.
- Raw terse errors ("Not found", "Failed to fetch", …) are wrapped in a clear,
  human sentence via `finishErrorMessage`.

Local-success, cloud, and needs-cloud-login paths are unchanged.

Tests: updated the affected conductor tests and added coverage for the
persistent-404 no-loop + retry + Settings escape, the cloud `error:retry`
re-run, and `provider:other` -> Settings exit. Also hardened `beforeEach` to
restore default mock implementations (clearAllMocks keeps implementations, so a
leaked mockRejectedValue would poison later tests). Full src/first-run suite:
191 passed.

Closes elizaOS#11882

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

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 15eabfee-96fc-4dde-b637-0b91834e0e47

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@lalalune

lalalune commented Jul 3, 2026

Copy link
Copy Markdown
Member

HOLD — the error-recovery half is right; the provider:other rewrite is a regression on healthy installs. On develop, provider:other ran finishLocal with configure-later: started the local runtime (the native agent on iOS/Android), persisted the active server, and POSTed /api/first-run (server-side meta.firstRunComplete). The PR replaces that with setTab("settings") + completeFirstRun("settings") (localStorage only). Consequences: (1) desktop/web BYOK users get re-onboarded on every reload (startup poll re-derives completion from the server: startup-phase-poll.ts:711-742); (2) on iOS/Android the local agent never starts → Settings has no backend, and relaunch hits the NO_SESSION+hadPriorFirstRun hard error wall (startup-coordinator.ts:155-162). (3) The special-case isn't needed for the loop fix — with the new non-looping ERROR_CHOICE, a persistent finish failure lands on the recoverable error turn.

To merge: restore the configure-later finish flow for provider:other (start local runtime + POST + persist active server), navigate to Settings on success, and let the new error turn own failures. The ERROR_CHOICE recovery work itself verified green (191/191 first-run tests on develop tip with the diff applied).

@lalalune

lalalune commented Jul 3, 2026

Copy link
Copy Markdown
Member

Superseded by maintainer-owned rebased PR #11906, which preserves the newer cloud-login resume work and includes the #11884 recovery behavior. #11906 has been merged to develop.

@lalalune lalalune closed this Jul 3, 2026
lalalune pushed a commit that referenced this pull request Jul 3, 2026
lalalune pushed a commit that referenced this pull request Jul 3, 2026
…instead of dead-ending to Settings; honest cloud-failure copy; completedRef guard (#11884 review fixes, fuzz pending)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

First-run onboarding loops forever on a finish error; 'Other / configure in Settings' never escapes

2 participants