Skip to content

fix(config): fall back to a usable saved provider instead of forcing full re-onboarding#410

Open
PierrunoYT wants to merge 1 commit into
Gitlawb:mainfrom
PierrunoYT:fix/no-active-provider-fallback
Open

fix(config): fall back to a usable saved provider instead of forcing full re-onboarding#410
PierrunoYT wants to merge 1 commit into
Gitlawb:mainfrom
PierrunoYT:fix/no-active-provider-fallback

Conversation

@PierrunoYT

@PierrunoYT PierrunoYT commented Jul 2, 2026

Copy link
Copy Markdown

Summary

When config.json has providers configured but none marked active (blank/stale activeProvider, or 2+ providers where none was ever selected), the interactive TUI discarded the already-normalized provider list and forced a full re-onboarding wizard — even though a usable, already-authenticated provider was already saved.

  • normalizeProviders/Resolve (internal/config/resolver.go) now return the normalized providers list alongside ErrNoActiveProvider instead of discarding it.
  • runInteractiveTUIWithSetup (internal/cli/app.go) tries the existing firstUsableProvider fallback on that list before forcing setup. Onboarding only runs when no configured provider is actually usable — the same behavior already used for a different flavor of this problem (active provider present but missing a credential).

Linked issue

Fixes #409

Checklist

  • The linked issue already has the issue-approved label.
  • go build ./..., go vet ./..., and go test ./... pass locally.
  • gofmt clean.
  • Tests added/updated for the change (and run under -race where relevant).
  • UI changes include screenshots or a short recording where possible.

Verification notes

  • go build ./... / go vet ./... clean.
  • Added TestResolveKeepsNormalizedProvidersWhenNoneMarkedActive (internal/config/resolver_test.go) and TestRunNoArgsFallsBackToUsableProviderWhenNoneMarkedActive (internal/cli/app_test.go); both pass, along with the pre-existing adjacent tests (TestRunNoArgsEntersSetupWhenResolveReportsNoActiveProvider, TestResolveRejectsActiveProviderWithoutConfiguredProfiles) confirming the "genuinely nothing configured" path is unchanged.
  • go test ./internal/config/... ./internal/cli/... has the same 8 pre-existing failures present on main before this change (no active provider/API key configured in this sandbox environment) — none are new or related to this change.
  • gofmt -l on the touched files flags them, but they were already flagged identically before this change (repo-wide core.autocrlf=true CRLF artifact on Windows) — not introduced here.
  • Not a UI change.

Summary by CodeRabbit

  • Bug Fixes
    • The app now continues with an available configured provider even when no active provider is marked, instead of always forcing setup.
    • Configuration resolution now preserves usable provider settings when the active provider is missing, allowing the app to fall back more gracefully.
    • Improved coverage ensures the CLI and configuration flow behave correctly in no-active-provider scenarios.

…full re-onboarding

When config.json has providers configured but none marked active
(e.g. a blank/stale activeProvider field, or a config with 2+
providers where none was ever selected), normalizeProviders threw
away the already-normalized provider list on ErrNoActiveProvider.
That meant the interactive TUI's existing "fall back to an
already-configured usable provider" logic (firstUsableProvider)
never got a chance to run for this case — the app instead wiped
resolved to a zero value and forced the user into a brand-new
onboarding wizard, even though a usable, already-saved provider
existed.

- normalizeProviders/Resolve now return the normalized providers
  list alongside ErrNoActiveProvider instead of discarding it.
- runInteractiveTUIWithSetup tries firstUsableProvider on that list
  before forcing setup; onboarding only runs when no configured
  provider is actually usable, matching the existing fallback
  already used for a different flavor of this problem
  (active-provider-present-but-no-credential).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The resolver now preserves the normalized provider list when resolution fails specifically due to a missing active provider (ErrNoActiveProvider), instead of discarding it. The interactive TUI setup flow uses this preserved list to fall back to a usable provider rather than forcing full re-onboarding. Tests validate both behaviors.

Changes

Provider fallback fix

Layer / File(s) Summary
Resolver preserves normalized providers
internal/config/resolver.go, internal/config/resolver_test.go
Resolve and normalizeProviders now return the normalized provider list alongside ErrNoActiveProvider instead of an empty/nil set; a new test confirms providers survive this failure case.
TUI falls back to usable provider
internal/cli/app.go, internal/cli/app_test.go
runInteractiveTUIWithSetup checks resolved.Providers for a usable provider via firstUsableProvider and sets Provider/ActiveProvider instead of unconditionally clearing config and forcing setup; a new test verifies the TUI launches without the setup wizard when a usable provider exists.

Estimated code review effort: 2 (Simple) | ~12 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI as runInteractiveTUIWithSetup
  participant Resolver as config.Resolve
  participant TUI as runTUI

  CLI->>Resolver: resolveConfig()
  Resolver-->>CLI: ResolvedConfig{Providers}, ErrNoActiveProvider
  CLI->>CLI: firstUsableProvider(resolved.Providers)
  alt usable provider found
    CLI->>CLI: set resolved.Provider, resolved.ActiveProvider
    CLI->>TUI: runTUI(resolved)
  else no usable provider
    CLI->>CLI: clear resolved, forceSetup=true
    CLI->>TUI: runTUI(forceSetup)
  end
Loading

Possibly related PRs

  • Gitlawb/zero#372: Both PRs update runInteractiveTUIWithSetup in internal/cli/app.go to treat config.ErrNoActiveProvider as non-fatal, with matching "no args" test coverage.
  • Gitlawb/zero#385: Both PRs modify the same interactive TUI setup/config-resolution flow across internal/cli/app.go and internal/config/resolver.go, addressing different resolver failure modes.

Suggested reviewers: gnanam1990, kevincodex1, anandh8x

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main behavior change: using an existing usable provider instead of forcing re-onboarding.
Linked Issues check ✅ Passed The changes preserve normalized providers and reuse an existing usable provider when activeProvider is missing, matching #409.
Out of Scope Changes check ✅ Passed The diff stays focused on the provider-fallback fix and its tests, with no obvious unrelated code changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
internal/cli/app.go (2)

566-572: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate firstUsableProvider fallback pattern.

The "find a usable provider and set resolved.Provider/resolved.ActiveProvider" logic appears twice — once here (566-572) and once at 599-603 for the needsSetup case. Extracting a small helper (e.g. applyUsableProviderFallback(resolved *config.ResolvedConfig) bool) would remove the duplication and make it a single spot to fix if the ErrProviderRequiresModel gap above is addressed later.

♻️ Suggested extraction
+func applyUsableProviderFallback(resolved *config.ResolvedConfig) bool {
+	usable, ok := firstUsableProvider(resolved.Providers)
+	if !ok {
+		return false
+	}
+	resolved.Provider = usable
+	resolved.ActiveProvider = usable.Name
+	return true
+}

Also applies to: 592-604

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/app.go` around lines 566 - 572, The fallback logic that calls
firstUsableProvider and sets resolved.Provider/resolved.ActiveProvider is
duplicated in the config handling flow. Extract this repeated branch into a
small helper such as applyUsableProviderFallback that accepts the resolved
config and returns whether a usable provider was applied, then use that helper
in both the current err/config.ErrNoActiveProvider path and the needsSetup path
to keep the behavior in one place.

560-572: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Fallback correctly gated to ErrNoActiveProvider.

Confirmed resolved.Providers is nil for the ErrProviderRequiresModel path (since normalizeProvidersWithOptions returns nil there), so firstUsableProvider naturally returns ok=false and the else branch is taken — no functional bug from the shared firstUsableProvider call preceding the &&.

One asymmetry worth a follow-up: if config.json has an active provider missing its model (triggering ErrProviderRequiresModel) but another fully usable profile exists, this branch still forces full re-onboarding instead of falling back — mirroring the exact UX problem issue #409 fixed for the "no active marked" case. Since normalizeProviders discards the normalized list on ErrProviderRequiresModel (resolver.go line 848), fixing this would require preserving providers there too. Given issue #409 explicitly scopes to ErrNoActiveProvider, this can be deferred to a follow-up.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/app.go` around lines 560 - 572, The fallback logic in the config
resolution branch only handles `config.ErrNoActiveProvider`, so
`ErrProviderRequiresModel` still wipes `resolved` and forces onboarding even
when another saved provider is usable. Update the resolution flow around
`firstUsableProvider`, `resolved.Providers`, and `normalizeProviders` so the
normalized provider list is preserved for the `ErrProviderRequiresModel` path as
well, then reuse the same fallback behavior instead of clearing `ResolvedConfig`
when a usable provider exists.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@internal/cli/app.go`:
- Around line 566-572: The fallback logic that calls firstUsableProvider and
sets resolved.Provider/resolved.ActiveProvider is duplicated in the config
handling flow. Extract this repeated branch into a small helper such as
applyUsableProviderFallback that accepts the resolved config and returns whether
a usable provider was applied, then use that helper in both the current
err/config.ErrNoActiveProvider path and the needsSetup path to keep the behavior
in one place.
- Around line 560-572: The fallback logic in the config resolution branch only
handles `config.ErrNoActiveProvider`, so `ErrProviderRequiresModel` still wipes
`resolved` and forces onboarding even when another saved provider is usable.
Update the resolution flow around `firstUsableProvider`, `resolved.Providers`,
and `normalizeProviders` so the normalized provider list is preserved for the
`ErrProviderRequiresModel` path as well, then reuse the same fallback behavior
instead of clearing `ResolvedConfig` when a usable provider exists.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 64e6ff66-f8a3-4623-b3b1-934a367e000a

📥 Commits

Reviewing files that changed from the base of the PR and between cdf9d83 and 1b8baa8.

📒 Files selected for processing (4)
  • internal/cli/app.go
  • internal/cli/app_test.go
  • internal/config/resolver.go
  • internal/config/resolver_test.go

@kevincodex1 kevincodex1 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.

looks good to me!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

no active provider marked forces full re-onboarding instead of using an already-configured provider

2 participants