Skip to content

fix: OAuth model cleanup, skill nav, UI polish#551

Merged
lefarcen merged 4 commits intomainfrom
fix/multi-bugfix-batch
Mar 25, 2026
Merged

fix: OAuth model cleanup, skill nav, UI polish#551
lefarcen merged 4 commits intomainfrom
fix/multi-bugfix-batch

Conversation

@alchemistklk
Copy link
Copy Markdown
Contributor

@alchemistklk alchemistklk commented Mar 25, 2026

Relates to #440

What

Batch of bugfixes from user testing feedback.

Why

Several issues reported during QA testing of OAuth flow, skill browsing, and UI details.

How

OAuth disconnect clears models

  • Disconnect route now calls deleteProvider() + ensureValidDefaultModel() + syncAll() so models don't linger in the selector after revoking OAuth
  • listModels() filters out OAuth-only providers whose token has expired

Skill detail back navigation

  • "Back to Skills" uses navigate(-1) to return to previous page instead of always resetting to initial tab
  • Skills page persists active tab (Yours/ClawHub) in URL search params (?tab=explore)

UI polish

  • Added space between "GitHub Issues" link and "反馈" in Chinese disclaimer
  • GitHub import tab input disabled with readOnly (coming soon)

Affected areas

  • apps/controller/src/routes/provider-oauth-routes.ts
  • apps/controller/src/services/model-provider-service.ts
  • apps/web/src/pages/skills.tsx
  • apps/web/src/pages/community-skill-detail.tsx
  • apps/web/src/components/skills/import-skill-modal.tsx

Checklist

  • pnpm typecheck passes
  • pnpm test passes for affected files
  • No new dependencies added

Summary by CodeRabbit

  • New Features

    • OAuth provider disconnection now properly cleans up stored models and syncs provider state.
    • Model availability now reflects OAuth connection status for supported providers.
    • Skills page tabs now persist in URL for bookmarking.
  • Bug Fixes

    • GitHub URL field in skill import modal is now read-only.

- OAuth disconnect now deletes provider model list and syncs config,
  so models don't linger in the selector after revoking OAuth
- OAuth-expired providers' models are excluded from listModels()
- Skill detail "Back to Skills" uses navigate(-1) to return to the
  previous page instead of always resetting to the initial tab
- Skills page persists active tab (Yours/ClawHub) in URL search params
  so navigating back preserves the selected tab
- GitHub import tab input disabled (coming soon)
- Added space between "GitHub Issues" link and "反馈" in disclaimer
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 25, 2026

📝 Walkthrough

Walkthrough

Wires OpenClawAuthService into ModelProviderService, adds OAuth-aware filtering of provider models, runs conditional cleanup and resync on OAuth disconnect, updates tests for disconnect behavior, makes GitHub URL input read-only in the import modal, and drives Skills page top tab from the URL query string.

Changes

Cohort / File(s) Summary
Container & Model Provider
apps/controller/src/app/container.ts, apps/controller/src/services/model-provider-service.ts
Container now injects OpenClawAuthService into ModelProviderService via setAuthService(). ModelProviderService gained setAuthService(), OAuth-aware filtering (OAUTH_PROVIDER_IDS, getExpiredOAuthProviderIds()), and refactored model listing helpers to exclude BYOK models for providers with expired OAuth tokens.
OAuth Disconnect Route
apps/controller/src/routes/provider-oauth-routes.ts
Disconnect handler reads prior OAuth status (wasConnected) and, only when disconnectOAuth() returns ok: true and provider was previously connected, performs deleteProvider(providerId), ensureValidDefaultModel(), and syncAll().
Route Tests
apps/controller/tests/provider-oauth-routes.test.ts, tests/controller/provider-oauth-routes.test.ts
Adds tests covering successful disconnect cleanup, failed disconnect path, and no-prior-connection path. Adds test alias import for cross-workspace test execution. Mocks extended with deleteProvider and ensureValidDefaultModel.
Web UI: Skills & Import Modal
apps/web/src/pages/skills.tsx, apps/web/src/components/skills/import-skill-modal.tsx
Skills page top tab state derived from URL query (tab=exploretopTab="explore"), and GitHub URL input in import modal is now disabled and readOnly.

Sequence Diagram

sequenceDiagram
    participant User
    participant Route as POST /api/v1/providers/{id}/oauth/disconnect
    participant AuthSvc as OpenClawAuthService
    participant ModelSvc as ModelProviderService
    participant SyncSvc as OpenClawSyncService

    User->>Route: request disconnect(providerId)
    Route->>AuthSvc: getProviderOAuthStatus(providerId)
    AuthSvc-->>Route: {connected: true/false}
    Route->>AuthSvc: disconnectOAuth(providerId)
    AuthSvc-->>Route: {ok: true/false}
    alt wasConnected AND ok:true
        Route->>ModelSvc: deleteProvider(providerId)
        ModelSvc-->>Route: {ok:true}
        Route->>ModelSvc: ensureValidDefaultModel()
        ModelSvc-->>Route: {ok}
        Route->>SyncSvc: syncAll()
        SyncSvc-->>Route: {ok}
        Route-->>User: {ok: true}
    else
        Route-->>User: {ok: false} or {ok: true} (no cleanup)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • lefarcen
  • PerishCode
  • nettee

Poem

🐰 Hop, hop — a tiny service link,
Models trimmed when tokens sink.
Disconnect sings a tidy tune,
Syncs and cleans beneath the moon.
Tabs remembered — import field sealed soon. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: OAuth model cleanup, skill nav, UI polish' accurately summarizes the three main areas addressed in the changeset: OAuth model cleanup, skill navigation, and UI polish improvements.
Description check ✅ Passed The PR description covers all required template sections with substantive content: What, Why, How, Affected areas, and Checklist are all completed with specific details about the changes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/multi-bugfix-batch

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 and usage tips.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Mar 25, 2026

Deploying nexu-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: 5457a3d
Status: ✅  Deploy successful!
Preview URL: https://428d017f.nexu-docs.pages.dev
Branch Preview URL: https://fix-multi-bugfix-batch.nexu-docs.pages.dev

View logs

@alchemistklk
Copy link
Copy Markdown
Contributor Author

/cr

@slack-code-review-channel
Copy link
Copy Markdown

✅ CR topic created in Feishu topic group Refly CR.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 102eaeaa03

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread apps/controller/src/routes/provider-oauth-routes.ts Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
apps/web/src/pages/skills.tsx (1)

268-273: Consider using functional update to preserve other search params.

The current implementation replaces all search params when switching tabs. If other params are added later (e.g., filters, pagination), they would be lost when toggling tabs.

♻️ Suggested safer pattern
 const [searchParams, setSearchParams] = useSearchParams();
 const topTab: TopTab =
   searchParams.get("tab") === "explore" ? "explore" : "yours";
 const setTopTab = (tab: TopTab) => {
-  setSearchParams(tab === "yours" ? {} : { tab }, { replace: true });
+  setSearchParams((prev) => {
+    const next = new URLSearchParams(prev);
+    if (tab === "yours") {
+      next.delete("tab");
+    } else {
+      next.set("tab", tab);
+    }
+    return next;
+  }, { replace: true });
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/pages/skills.tsx` around lines 268 - 273, The setTopTab
implementation currently calls setSearchParams with a new object and clobbers
any existing query parameters; update it to use the functional form of
setSearchParams so you can read and mutate the existing URLSearchParams instead
of replacing them wholesale: inside setTopTab (and keep topTab and
useSearchParams unchanged) call setSearchParams(prev => { const params = new
URLSearchParams(prev); if (tab === "yours") params.delete("tab"); else
params.set("tab", "explore"); return params; }, { replace: true }) so other
params (filters, page, etc.) are preserved when toggling tabs.
apps/controller/tests/provider-oauth-routes.test.ts (1)

202-219: Assert the rest of the cleanup path stays untouched on ok: false.

This failure-path test only guards deleteProvider(). If the handler regresses and still calls ensureValidDefaultModel() or syncAll(), this case stays green.

✅ Tighten the failure-path assertions
     await expect(response.json()).resolves.toEqual({ ok: false });
     expect(
       container.modelProviderService.deleteProvider,
     ).not.toHaveBeenCalled();
+    expect(
+      container.modelProviderService.ensureValidDefaultModel,
+    ).not.toHaveBeenCalled();
+    expect(container.openclawSyncService.syncAll).not.toHaveBeenCalled();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/controller/tests/provider-oauth-routes.test.ts` around lines 202 - 219,
Tighten the failure-path test so it also asserts other cleanup routines are not
invoked when disconnectOAuth resolves to false: after the existing expects add
assertions that container.modelProviderService.ensureValidDefaultModel and the
sync method (container.providerSyncService.syncAll) were not called; this
ensures the handler doesn't call ensureValidDefaultModel() or syncAll() on an
OAuth disconnect failure.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/controller/src/routes/provider-oauth-routes.ts`:
- Around line 140-145: After disconnectOAuth() returns true, make the cleanup
calls (container.modelProviderService.deleteProvider(providerId),
container.modelProviderService.ensureValidDefaultModel(),
container.openclawSyncService.syncAll()) failure-safe so they cannot cause the
handler to return 500; wrap them so errors are caught and logged but not
rethrown (for example use Promise.allSettled([...]) or individual try/catch
blocks and processLogger.error on failure), ensuring the auth state removal
remains final and the HTTP response is not impacted by post-disconnect cleanup
errors.

---

Nitpick comments:
In `@apps/controller/tests/provider-oauth-routes.test.ts`:
- Around line 202-219: Tighten the failure-path test so it also asserts other
cleanup routines are not invoked when disconnectOAuth resolves to false: after
the existing expects add assertions that
container.modelProviderService.ensureValidDefaultModel and the sync method
(container.providerSyncService.syncAll) were not called; this ensures the
handler doesn't call ensureValidDefaultModel() or syncAll() on an OAuth
disconnect failure.

In `@apps/web/src/pages/skills.tsx`:
- Around line 268-273: The setTopTab implementation currently calls
setSearchParams with a new object and clobbers any existing query parameters;
update it to use the functional form of setSearchParams so you can read and
mutate the existing URLSearchParams instead of replacing them wholesale: inside
setTopTab (and keep topTab and useSearchParams unchanged) call
setSearchParams(prev => { const params = new URLSearchParams(prev); if (tab ===
"yours") params.delete("tab"); else params.set("tab", "explore"); return params;
}, { replace: true }) so other params (filters, page, etc.) are preserved when
toggling tabs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 49bce8b1-5679-4b8f-becb-64700a915c09

📥 Commits

Reviewing files that changed from the base of the PR and between 1c368ed and 102eaea.

📒 Files selected for processing (7)
  • apps/controller/src/app/container.ts
  • apps/controller/src/routes/provider-oauth-routes.ts
  • apps/controller/src/services/model-provider-service.ts
  • apps/controller/tests/provider-oauth-routes.test.ts
  • apps/web/src/components/skills/import-skill-modal.tsx
  • apps/web/src/pages/skills.tsx
  • tests/controller/provider-oauth-routes.test.ts

Comment thread apps/controller/src/routes/provider-oauth-routes.ts Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/controller/src/services/model-provider-service.ts (1)

259-260: Avoid a third hardcoded OAuth provider registry.

"openai" is already encoded in apps/controller/src/runtime/openclaw-auth-profiles-store.ts:16-18 and apps/controller/src/lib/openclaw-config-compiler.ts:37-39, while this file also has separate MiniMax OAuth handling. Pull this from a shared source—or rename it to the narrower concept it actually represents—so provider capability data does not drift across files.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/controller/src/services/model-provider-service.ts` around lines 259 -
260, Replace the hardcoded OAUTH_PROVIDER_IDS set with the canonical/shared
source or narrow its meaning: either import the central OAuth-capability
registry used elsewhere and replace the local constant OAUTH_PROVIDER_IDS with
that shared symbol, or if this set only reflects MiniMax-specific behavior,
rename OAUTH_PROVIDER_IDS to a narrower identifier (e.g.,
MINIMAX_OAUTH_PROVIDER_IDS) and update usages in the model-provider-service
methods to match; ensure you remove the duplicated literal array and reference
the single source of truth so provider capability data no longer drifts across
files.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/controller/src/services/model-provider-service.ts`:
- Around line 359-377: The current getExpiredOAuthProviderIds marks providers as
expired whenever openclawAuthService.getProviderOAuthStatus returns connected:
false, but that conflates transient read errors with real expirations; modify
getExpiredOAuthProviderIds so it only treats a provider as expired when the auth
status explicitly signals expiration (e.g. status.isExpired === true or a
dedicated status.type === 'expired') and do not add provider.providerId on
transient errors or generic connected: false responses—catch thrown errors from
OpenClawAuthService.getProviderOAuthStatus and treat them as non-expired (or log
and continue), and update callers like ensureValidDefaultModel to rely on this
explicit isExpired signal rather than plain connected: false.

---

Nitpick comments:
In `@apps/controller/src/services/model-provider-service.ts`:
- Around line 259-260: Replace the hardcoded OAUTH_PROVIDER_IDS set with the
canonical/shared source or narrow its meaning: either import the central
OAuth-capability registry used elsewhere and replace the local constant
OAUTH_PROVIDER_IDS with that shared symbol, or if this set only reflects
MiniMax-specific behavior, rename OAUTH_PROVIDER_IDS to a narrower identifier
(e.g., MINIMAX_OAUTH_PROVIDER_IDS) and update usages in the
model-provider-service methods to match; ensure you remove the duplicated
literal array and reference the single source of truth so provider capability
data no longer drifts across files.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 01d915e3-1341-4851-965e-9abbd0f3948c

📥 Commits

Reviewing files that changed from the base of the PR and between 102eaea and 7711ab3.

📒 Files selected for processing (4)
  • apps/controller/src/app/container.ts
  • apps/controller/src/routes/provider-oauth-routes.ts
  • apps/controller/src/services/model-provider-service.ts
  • apps/controller/tests/provider-oauth-routes.test.ts
✅ Files skipped from review due to trivial changes (1)
  • apps/controller/src/app/container.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/controller/src/routes/provider-oauth-routes.ts

Comment thread apps/controller/src/services/model-provider-service.ts
@lefarcen lefarcen merged commit fc31260 into main Mar 25, 2026
7 checks passed
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.

2 participants