Skip to content

fix(agents): actionable copy for exhausted auth-profile failover#85798

Merged
RomneyDa merged 3 commits into
mainfrom
auth-profile-error-message
Jun 1, 2026
Merged

fix(agents): actionable copy for exhausted auth-profile failover#85798
RomneyDa merged 3 commits into
mainfrom
auth-profile-error-message

Conversation

@RomneyDa

Copy link
Copy Markdown
Member

Summary

When every auth profile for a provider was in cooldown, the TUI surfaced this:

Agent failed before reply: No available auth profile for openai-codex (all in cooldown or unavailable).

The message gave the user nothing to act on, even though the failover machinery had already resolved a concrete reason (auth, billing, rate_limit, session_expired, …) and the repo already had a helper that knows how to render a provider-specific openclaw models auth login --provider <id> recovery hint.

This PR routes the resolved reason through a single presenter so the user-facing copy says, e.g.:

All auth profiles for openai-codex are unusable (authentication failed or session expired). Run openclaw models auth login --provider openai-codex or openclaw configure.

…and falls back to the original provider error text when no actionable reason maps.

Logic followed

The smell wasn't the wording — it was that the system already had structured failure data (FailoverError.reason) but the user-facing layer was either ignoring it or, downstream, regex-matching the message string to try to recover it. Adding another regex would deepen that smell. So the architectural move is to invert: make structured data flow through, and treat strings as fallback, not as the source of truth.

Concretely:

  1. Single presenter for auth-profile failures. New src/agents/auth-profiles/failure-copy.ts exports formatAuthProfileFailureMessage({ reason, provider, allInCooldown, cause?, config?, workspaceDir?, env? }). Pure function — one place owns the copy for every "auth profiles exhausted / unusable" surface, trivially unit-testable.

  2. Use it at the source. throwAuthProfileFailover in src/agents/pi-embedded-runner/run/auth-controller.ts already computed reason via resolveAuthProfileFailoverReason and discarded it for the message. It now feeds reason (plus the underlying cause, if any) into the presenter, so FailoverError.message ships with good copy no matter who reads it (TUI, logs, CLI). Existing FailoverError.reason metadata is unchanged.

  3. Cross-layer move. buildProviderAuthRecoveryHint lived in src/commands/ but the new caller is in src/agents/, and non-test code in src/agents/ must not import from src/commands/. The helper (and its private resolveProviderAuthLoginCommand) moved to src/agents/provider-auth-recovery-hint.ts. src/commands/provider-auth-guidance.ts becomes a one-line re-export so the existing CLI callers (doctor-auth.ts, auth-choice.model-check.ts, models/list.status-command.ts) keep working without churn.

  4. Backward-safe fallback. When the resolved reason has no actionable copy (e.g. unknown / empty_response) and a cause is present, the presenter returns the cause's text verbatim — so paths that previously produced decent getApiKeyForModel-style errors are never regressed.

What was deliberately not done

  • No downstream string-classifier change. The classifiers in agent-runner-execution.ts (buildMissingApiKeyFailureText, classifyOAuthRefreshFailure) still run; they're now redundant for the auth-profile path but they correctly handle errors from third-party transports that arrive as opaque strings. Removing/typing them is a follow-up worth a separate PR.
  • No new \"no_profile\" reason. The profileCandidates = [undefined] path (no profile configured at all) still surfaces getApiKeyForModel's message. Unifying that path through the presenter is a sibling follow-up.
  • No envelope work for cross-process FailoverError serialization. This PR fixes the message at the source, where the type is already in scope.

Test plan

  • pnpm vitest run src/agents/auth-profiles/failure-copy.test.ts — new presenter unit tests (reason coverage, hint inclusion rules, cause passthrough, cause-dedup).
  • pnpm vitest run src/agents/pi-embedded-runner/run/auth-controller.test.ts src/commands/provider-auth-guidance.test.ts — direct callers.
  • pnpm vitest run src/commands/models/list.status.test.ts src/commands/auth-choice.model-check.test.ts src/commands/provider-setup-cold-imports.test.ts — all existing consumers of the moved helper.
  • pnpm tsgo:core + pnpm tsgo:core:test — clean.
  • npx oxlint on touched files — clean.
  • Manual: trigger an openai-codex failure with all profiles in cooldown and verify the TUI now shows the reason + login command.

@RomneyDa
RomneyDa requested a review from a team as a code owner May 23, 2026 18:29
@openclaw-barnacle openclaw-barnacle Bot added commands Command implementations agents Agent runtime and tooling size: M maintainer Maintainer-authored PR labels May 23, 2026
@clawsweeper

clawsweeper Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs changes before merge. Reviewed June 1, 2026, 1:48 PM ET / 17:48 UTC.

Summary
The PR adds a shared auth-profile failure-message presenter, wires embedded-runner auth failover through it, moves the provider auth recovery hint helper into src/agents/ with a command-layer re-export, adds focused presenter tests, and edits CHANGELOG.md.

PR surface: Source +141, Tests +103, Docs +1. Total +245 across 6 files.

Reproducibility: yes. Source inspection shows the current all-profiles-in-cooldown path reaches throwAuthProfileFailover({ allInCooldown: true }) and falls back to the generic exhausted-profile message; I did not run the live TUI path in this read-only review.

Review metrics: none identified.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🌊 off-meta tidepool
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • [P2] Remove the CHANGELOG.md entry before merge.

Risk before merge

  • [P1] The PR still edits release-owned CHANGELOG.md; merging as-is would put normal PR release-note text into a file generated by the release process.

Maintainer options:

  1. Decide the mitigation before merge
    Keep the centralized presenter and helper move, remove the CHANGELOG.md entry, and leave release-note context in the PR body or squash message for release generation.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • [P2] A single mechanical repair remains: remove the release-owned changelog edit while leaving the runtime and tests intact for maintainer review.

Security
Cleared: The diff does not change dependencies, lockfiles, CI, permissions, credential storage, or code-execution surfaces; no concrete security or supply-chain concern found.

Review findings

  • [P3] Remove the release-owned changelog entry — CHANGELOG.md:660
Review details

Best possible solution:

Keep the centralized presenter and helper move, remove the CHANGELOG.md entry, and leave release-note context in the PR body or squash message for release generation.

Do we have a high-confidence way to reproduce the issue?

Yes. Source inspection shows the current all-profiles-in-cooldown path reaches throwAuthProfileFailover({ allInCooldown: true }) and falls back to the generic exhausted-profile message; I did not run the live TUI path in this read-only review.

Is this the best way to solve the issue?

Yes, with one cleanup. Formatting the message at the auth-profile failover source is a better owner-boundary fix than adding downstream string classifiers, and the command helper move avoids an agents-to-commands dependency; the changelog edit should be dropped.

Full review comments:

  • [P3] Remove the release-owned changelog entry — CHANGELOG.md:660
    CHANGELOG.md is release-owned in this repo, and normal PRs should keep release-note context in the PR body or squash message instead. The PR body already has the useful release-note context, so please drop this file change before merge.
    Confidence: 0.97

Overall correctness: patch is correct
Overall confidence: 0.88

AGENTS.md: found and applied where relevant.

Codex review notes: model gpt-5.5, reasoning high; reviewed against ee4802802843.

Label changes

Label justifications:

  • P3: This is a low-risk, maintainer-authored user-facing error-copy improvement with one release-process cleanup before merge.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🌊 off-meta tidepool and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Not applicable: The PR is member-authored and labeled for maintainer handling, so the external contributor real-behavior-proof gate does not apply; the body lists unit/type/lint validation, with live TUI proof still optional for maintainer confidence.
Evidence reviewed

PR surface:

Source +141, Tests +103, Docs +1. Total +245 across 6 files.

View PR surface stats
Area Files Added Removed Net
Source 4 219 78 +141
Tests 1 103 0 +103
Docs 1 1 0 +1
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 6 323 78 +245

Acceptance criteria:

  • [P1] git diff --check.

What I checked:

Likely related people:

  • Peter Steinberger: Auth-controller split and provider-auth guidance refactors appear in the relevant history, including the path split that led to the current embedded runner surface. (role: feature-history owner; confidence: medium; commits: 18dc98b00e43, ef1784d264e0, e93216080aa1; files: src/agents/embedded-agent-runner/run/auth-controller.ts, src/commands/provider-auth-guidance.ts)
  • Vincent Koc: Recent history/blame on the current auth-controller, provider-auth guidance, and auth-profile usage files points to recent maintenance in the affected auth/runtime area. (role: recent area contributor; confidence: medium; commits: a4847297b85c, 95517edaeba4; files: src/agents/embedded-agent-runner/run/auth-controller.ts, src/commands/provider-auth-guidance.ts, src/agents/auth-profiles/usage.ts)
  • Vignesh Natarajan: The structured auth-profile unavailable-reason behavior used by this PR traces to the earlier Agents: infer auth-profile unavailable failover reason change. (role: introduced behavior; confidence: medium; commits: 5c7c37a02a3b; files: src/agents/auth-profiles/usage.ts, src/agents/model-fallback.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P2 Normal backlog priority with limited blast radius. labels May 23, 2026
@clawsweeper

clawsweeper Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper PR egg

✨ Hatched: 🥚 common Pearl Proofling

Hatch command

Comment @clawsweeper hatch when this PR is hatchable.

Hatchability rules:

  • Merged PRs are hatchable.
  • Open PRs are hatchable when they are status: 👀 ready for maintainer look, status: 🚀 automerge armed, or labeled clawsweeper:automerge.
  • Closed unmerged PRs are hatchable only when one of those hatchable labels is still present in the durable record.

Rarity: 🥚 common.
Trait: guards the happy path.
Image traits: location status garden; accessory proof snapshot camera; palette seafoam, black, and opal; mood determined; pose curling around a status light; shell soft velvet shell; lighting tiny status-light glow; background tiny shells and proof notes.
Share on X: post this hatch
Copy: My PR egg hatched a 🥚 common Pearl Proofling in ClawSweeper.

What is this egg doing here?
  • Eggs appear after the PR passes real-behavior proof. It is here for vibes, not verdicts: it does not change labels, ratings, merge decisions, or automation.
  • The shell reacts to review momentum: open follow-up work warms it up, re-review makes it wobble, and a clean final review lets it hatch.
  • Hatchability usually comes from sufficient real-behavior proof, no blocking P0/P1/P2 findings, no security attention needed, and clean correctness. A merged PR is already final, so merge makes the egg hatchable independently.
  • The hatch is seeded from this repository and PR number, so the same PR keeps the same creature; the reviewed head SHA can only change safe visual details.
  • Rarity is just collectible sparkle: 🥚 common, 🌱 uncommon, 💎 rare, ✨ glimmer, and 🌈 legendary.

@clawsweeper clawsweeper Bot added P3 Low-priority cleanup, docs, polish, ergonomics, or speculative work. and removed P2 Normal backlog priority with limited blast radius. labels May 23, 2026
@altaywtf
altaywtf self-requested a review May 26, 2026 21:36

@altaywtf altaywtf left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nice one!

@RomneyDa
RomneyDa marked this pull request as draft May 27, 2026 20:02
The pi-embedded runner threw a generic "No available auth profile for
<provider> (all in cooldown or unavailable)" message whenever every
configured profile was in cooldown, even though the failover machinery
had already resolved a concrete reason (auth, billing, rate_limit,
session_expired, etc.). The user-facing copy never used that reason and
never told the user how to recover.

Route the resolved reason through a single presenter
(`formatAuthProfileFailureMessage`) that composes a reason-specific
sentence with `buildProviderAuthRecoveryHint`, so FailoverError.message
ships with the right `openclaw models auth login --provider <id>` hint
when the cause is authentication/session/billing, and falls back to the
underlying provider error text otherwise. Helper moved out of
`src/commands/` into `src/agents/` because `src/agents/` cannot depend
on `src/commands/`.
@RomneyDa
RomneyDa force-pushed the auth-profile-error-message branch from 86c577b to 927d171 Compare June 1, 2026 17:40
@RomneyDa
RomneyDa marked this pull request as ready for review June 1, 2026 17:59
…-copy tests

- Delete `src/commands/provider-auth-guidance.ts` and point doctor-auth, auth-choice.model-check, and models/list.status-command directly at `src/agents/provider-auth-recovery-hint.ts`. The cold-imports test moves with it.
- Rewrite `failure-copy.test.ts` to assert behavior (recovery-hint dispatch, provider mention, cause-suffix dedup) instead of pinning exact long copy strings, so wording tweaks no longer require a test update in two places.
@RomneyDa
RomneyDa merged commit 64a946a into main Jun 1, 2026
158 checks passed
@RomneyDa
RomneyDa deleted the auth-profile-error-message branch June 1, 2026 18:16
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jun 2, 2026
…nclaw#85798)

* fix(agents): actionable copy for exhausted auth-profile failover

The pi-embedded runner threw a generic "No available auth profile for
<provider> (all in cooldown or unavailable)" message whenever every
configured profile was in cooldown, even though the failover machinery
had already resolved a concrete reason (auth, billing, rate_limit,
session_expired, etc.). The user-facing copy never used that reason and
never told the user how to recover.

Route the resolved reason through a single presenter
(`formatAuthProfileFailureMessage`) that composes a reason-specific
sentence with `buildProviderAuthRecoveryHint`, so FailoverError.message
ships with the right `openclaw models auth login --provider <id>` hint
when the cause is authentication/session/billing, and falls back to the
underlying provider error text otherwise. Helper moved out of
`src/commands/` into `src/agents/` because `src/agents/` cannot depend
on `src/commands/`.

* fix(agents): soften auth-profile failure copy for non-technical users

* refactor(agents): drop guidance re-export shim and de-brittle failure-copy tests

- Delete `src/commands/provider-auth-guidance.ts` and point doctor-auth, auth-choice.model-check, and models/list.status-command directly at `src/agents/provider-auth-recovery-hint.ts`. The cold-imports test moves with it.
- Rewrite `failure-copy.test.ts` to assert behavior (recovery-hint dispatch, provider mention, cause-suffix dedup) instead of pinning exact long copy strings, so wording tweaks no longer require a test update in two places.
SYU8384 pushed a commit to SYU8384/openclaw that referenced this pull request Jun 3, 2026
…nclaw#85798)

* fix(agents): actionable copy for exhausted auth-profile failover

The pi-embedded runner threw a generic "No available auth profile for
<provider> (all in cooldown or unavailable)" message whenever every
configured profile was in cooldown, even though the failover machinery
had already resolved a concrete reason (auth, billing, rate_limit,
session_expired, etc.). The user-facing copy never used that reason and
never told the user how to recover.

Route the resolved reason through a single presenter
(`formatAuthProfileFailureMessage`) that composes a reason-specific
sentence with `buildProviderAuthRecoveryHint`, so FailoverError.message
ships with the right `openclaw models auth login --provider <id>` hint
when the cause is authentication/session/billing, and falls back to the
underlying provider error text otherwise. Helper moved out of
`src/commands/` into `src/agents/` because `src/agents/` cannot depend
on `src/commands/`.

* fix(agents): soften auth-profile failure copy for non-technical users

* refactor(agents): drop guidance re-export shim and de-brittle failure-copy tests

- Delete `src/commands/provider-auth-guidance.ts` and point doctor-auth, auth-choice.model-check, and models/list.status-command directly at `src/agents/provider-auth-recovery-hint.ts`. The cold-imports test moves with it.
- Rewrite `failure-copy.test.ts` to assert behavior (recovery-hint dispatch, provider mention, cause-suffix dedup) instead of pinning exact long copy strings, so wording tweaks no longer require a test update in two places.
sablehead pushed a commit to sablehead/openclaw that referenced this pull request Jun 10, 2026
…nclaw#85798)

* fix(agents): actionable copy for exhausted auth-profile failover

The pi-embedded runner threw a generic "No available auth profile for
<provider> (all in cooldown or unavailable)" message whenever every
configured profile was in cooldown, even though the failover machinery
had already resolved a concrete reason (auth, billing, rate_limit,
session_expired, etc.). The user-facing copy never used that reason and
never told the user how to recover.

Route the resolved reason through a single presenter
(`formatAuthProfileFailureMessage`) that composes a reason-specific
sentence with `buildProviderAuthRecoveryHint`, so FailoverError.message
ships with the right `openclaw models auth login --provider <id>` hint
when the cause is authentication/session/billing, and falls back to the
underlying provider error text otherwise. Helper moved out of
`src/commands/` into `src/agents/` because `src/agents/` cannot depend
on `src/commands/`.

* fix(agents): soften auth-profile failure copy for non-technical users

* refactor(agents): drop guidance re-export shim and de-brittle failure-copy tests

- Delete `src/commands/provider-auth-guidance.ts` and point doctor-auth, auth-choice.model-check, and models/list.status-command directly at `src/agents/provider-auth-recovery-hint.ts`. The cold-imports test moves with it.
- Rewrite `failure-copy.test.ts` to assert behavior (recovery-hint dispatch, provider mention, cause-suffix dedup) instead of pinning exact long copy strings, so wording tweaks no longer require a test update in two places.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling commands Command implementations maintainer Maintainer-authored PR P3 Low-priority cleanup, docs, polish, ergonomics, or speculative work. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: M status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants