From aed4d330f4b2ebb1d18d0731e97b7b6232676c01 Mon Sep 17 00:00:00 2001 From: Sahil Satralkar <62758655+sahilsatralkar@users.noreply.github.com> Date: Thu, 19 Feb 2026 22:55:21 +0530 Subject: [PATCH 1/4] Auto-reply: improve user-facing error messages --- CHANGELOG.md | 1 + ...ets-active-session-native-stop.e2e.test.ts | 20 +++++++- .../reply/agent-runner-execution.ts | 49 +++++++++++++------ .../agent-runner.runreplyagent.e2e.test.ts | 2 +- 4 files changed, 56 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5673d2dd5f5..78ee8ec31fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2192,6 +2192,7 @@ Docs: https://docs.openclaw.ai - Security/Exec: block grep safe-bin positional operand bypass by setting grep positional budget to zero, so `-e/--regexp` cannot smuggle bare filename reads (for example `.env`) via ambiguous positionals; safe-bin grep patterns must come from `-e/--regexp`. Thanks @athuljayaram for reporting. - Security/Gateway/Agents: remove implicit admin scopes from agent tool gateway calls by classifying methods to least-privilege operator scopes, and enforce owner-only tooling (`cron`, `gateway`, `whatsapp_login`) through centralized tool-policy wrappers plus tool metadata to prevent non-owner DM privilege escalation. Ships in the next npm release. Thanks @Adam55A-code for reporting. - Security/Gateway: centralize gateway method-scope authorization and default non-CLI gateway callers to least-privilege method scopes, with explicit CLI scope handling, full core-handler scope classification coverage, and regression guards to prevent scope drift. +- Auto-reply: show user-friendly error messages based on error type (rate limit, auth, billing, timeout) instead of exposing technical details. - Security/Net: block SSRF bypass via NAT64 (`64:ff9b::/96`, `64:ff9b:1::/48`), 6to4 (`2002::/16`), and Teredo (`2001:0000::/32`) IPv6 transition addresses, and fail closed on IPv6 parse errors. Thanks @jackhax. - Security/OTEL: sanitize OTLP endpoint URL resolution. (#13791) Thanks @vincentkoc. - Security: patch Dependabot security issues in pnpm lock. (#20832) Thanks @vincentkoc. diff --git a/src/auto-reply/reply.triggers.trigger-handling.targets-active-session-native-stop.e2e.test.ts b/src/auto-reply/reply.triggers.trigger-handling.targets-active-session-native-stop.e2e.test.ts index cec3652d4a9..4ae822c7f35 100644 --- a/src/auto-reply/reply.triggers.trigger-handling.targets-active-session-native-stop.e2e.test.ts +++ b/src/auto-reply/reply.triggers.trigger-handling.targets-active-session-native-stop.e2e.test.ts @@ -154,13 +154,31 @@ describe("trigger handling", () => { { error: "sandbox is not defined.", expected: - "⚠️ Agent failed before reply: sandbox is not defined.\nLogs: openclaw logs --follow", + "Something unexpected happened. Try /new to start a fresh conversation, or try again in a moment.", }, { error: "Context window exceeded", expected: "⚠️ Context overflow — prompt too large for this model. Try a shorter message or a larger-context model.", }, + { + error: "rate_limit_exceeded: API rate limit exceeded", + expected: "The AI service is busy. Please wait a moment and try again.", + }, + { + error: "401 Unauthorized: Invalid API key", + expected: + "I couldn't connect to the AI service. Please verify your API key is configured correctly.", + }, + { + error: "402 Payment Required: billing limit exceeded", + expected: + "I've reached my limit with the AI service. Please check your account balance and try again.", + }, + { + error: "408 Request Timeout: connection timed out", + expected: "The request timed out. Please try again, or start a fresh session with /new.", + }, ] as const; for (const testCase of errorCases) { runEmbeddedPiAgentMock.mockClear(); diff --git a/src/auto-reply/reply/agent-runner-execution.ts b/src/auto-reply/reply/agent-runner-execution.ts index 9ebc239f7ff..7ca57369e50 100644 --- a/src/auto-reply/reply/agent-runner-execution.ts +++ b/src/auto-reply/reply/agent-runner-execution.ts @@ -6,13 +6,15 @@ import { getCliSessionId } from "../../agents/cli-session.js"; import { runWithModelFallback } from "../../agents/model-fallback.js"; import { isCliProvider } from "../../agents/model-selection.js"; import { - BILLING_ERROR_USER_MESSAGE, + isAuthErrorMessage, + isBillingErrorMessage, isCompactionFailureError, isContextOverflowError, - isBillingErrorMessage, isLikelyContextOverflowError, + isOverloadedErrorMessage, + isRateLimitErrorMessage, + isTimeoutErrorMessage, isTransientHttpError, - sanitizeUserFacingText, } from "../../agents/pi-embedded-helpers.js"; import { runEmbeddedPiAgent } from "../../agents/pi-embedded.js"; import { @@ -534,6 +536,12 @@ export async function runAgentTurnWithFallback(params: { const isRoleOrderingError = /incorrect role information|roles must alternate/i.test(message); const isTransientHttp = isTransientHttpError(message); + const isRateLimit = isRateLimitErrorMessage(message); + const isAuthError = isAuthErrorMessage(message); + const isBillingError = isBillingErrorMessage(message); + const isTimeoutError = isTimeoutErrorMessage(message); + const isOverloaded = isOverloadedErrorMessage(message); + if ( isCompactionFailure && !didResetAfterCompactionFailure && @@ -620,17 +628,30 @@ export async function runAgentTurnWithFallback(params: { } defaultRuntime.error(`Embedded agent failed before reply: ${message}`); - const safeMessage = isTransientHttp - ? sanitizeUserFacingText(message, { errorContext: true }) - : message; - const trimmedMessage = safeMessage.replace(/\.\s*$/, ""); - const fallbackText = isBilling - ? BILLING_ERROR_USER_MESSAGE - : isContextOverflow - ? "⚠️ Context overflow — prompt too large for this model. Try a shorter message or a larger-context model." - : isRoleOrderingError - ? "⚠️ Message ordering conflict - please try again. If this persists, use /new to start a fresh session." - : `⚠️ Agent failed before reply: ${trimmedMessage}.\nLogs: openclaw logs --follow`; + + let fallbackText: string; + + if (isContextOverflow) { + fallbackText = + "⚠️ Context overflow — prompt too large for this model. Try a shorter message or a larger-context model."; + } else if (isRoleOrderingError) { + fallbackText = + "⚠️ Message ordering conflict - please try again. If this persists, use /new to start a fresh session."; + } else if (isRateLimit || isOverloaded) { + fallbackText = "The AI service is busy. Please wait a moment and try again."; + } else if (isAuthError) { + fallbackText = + "I couldn't connect to the AI service. Please verify your API key is configured correctly."; + } else if (isBillingError) { + fallbackText = + "I've reached my limit with the AI service. Please check your account balance and try again."; + } else if (isTimeoutError) { + fallbackText = + "The request timed out. Please try again, or start a fresh session with /new."; + } else { + fallbackText = + "Something unexpected happened. Try /new to start a fresh conversation, or try again in a moment."; + } return { kind: "final", diff --git a/src/auto-reply/reply/agent-runner.runreplyagent.e2e.test.ts b/src/auto-reply/reply/agent-runner.runreplyagent.e2e.test.ts index 6bebdc6a390..36fda14def4 100644 --- a/src/auto-reply/reply/agent-runner.runreplyagent.e2e.test.ts +++ b/src/auto-reply/reply/agent-runner.runreplyagent.e2e.test.ts @@ -1485,7 +1485,7 @@ describe("runReplyAgent typing (heartbeat)", () => { const res = await run(); expect(res).toMatchObject({ - text: expect.stringContaining("Agent failed before reply"), + text: "Something unexpected happened. Try /new to start a fresh conversation, or try again in a moment.", }); expect(sessionStore.main).toBeDefined(); await expect(fs.access(transcriptPath)).resolves.toBeUndefined(); From d9818d135782e09229f66f4535daa0cfbf0dcdb7 Mon Sep 17 00:00:00 2001 From: Sahil Satralkar <62758655+sahilsatralkar@users.noreply.github.com> Date: Thu, 19 Feb 2026 23:40:00 +0530 Subject: [PATCH 2/4] docs: include build plan as reference --- docs/plan-user-friendly-error-messages.md | 298 ++++++++++++++++++++++ 1 file changed, 298 insertions(+) create mode 100644 docs/plan-user-friendly-error-messages.md diff --git a/docs/plan-user-friendly-error-messages.md b/docs/plan-user-friendly-error-messages.md new file mode 100644 index 00000000000..8c34d622762 --- /dev/null +++ b/docs/plan-user-friendly-error-messages.md @@ -0,0 +1,298 @@ +# Execution Plan: Improve User-Friendly Error Messages + +> **IMPORTANT:** This plan file must NOT be committed to the repository. Keep it for reference and PR description. + +--- + +## Phase 1: Preparation (Steps 0-3) + +- [ ] **Step 0: Verify Clean Working Tree** + + ```bash + git status + ``` + + Ensure working tree is clean (no uncommitted changes) before starting. + +- [ ] **Step 1: Run Baseline Tests** + + ```bash + pnpm test src/auto-reply/reply/agent-runner.runreplyagent.test.ts + pnpm vitest run --config vitest.e2e.config.ts src/auto-reply/reply.triggers.trigger-handling.includes-error-cause-embedded-agent-throws.e2e.test.ts + ``` + +- [ ] **Step 2: Verify Branch** + + ```bash + git branch + ``` + + Should show: `refactor/improve-error-messages` + +- [ ] **Step 3: Verify Imports Available** + Confirm these functions exist in `src/agents/pi-embedded-helpers.ts`: + - `isRateLimitErrorMessage()` + - `isAuthErrorMessage()` + - `isBillingErrorMessage()` + - `isTimeoutErrorMessage()` + - `isOverloadedErrorMessage()` + +--- + +## Phase 2: Code Changes (Steps 4-6) + +- [ ] **Step 4: Edit `src/auto-reply/reply/agent-runner-execution.ts`** + + #### 4.1 Add imports (replace lines 7-13): + + ```typescript + import { + isAuthErrorMessage, + isBillingErrorMessage, + isCompactionFailureError, + isContextOverflowError, + isLikelyContextOverflowError, + isOverloadedErrorMessage, + isRateLimitErrorMessage, + isTimeoutErrorMessage, + isTransientHttpError, + sanitizeUserFacingText, + } from "../../agents/pi-embedded-helpers.js"; + ``` + + #### 4.2 Add error classification after line 428: + + ```typescript + // Classify error type for user-friendly messaging + const isRateLimit = isRateLimitErrorMessage(message); + const isAuthError = isAuthErrorMessage(message); + const isBillingError = isBillingErrorMessage(message); + const isTimeoutError = isTimeoutErrorMessage(message); + const isOverloaded = isOverloadedErrorMessage(message); + ``` + + #### 4.3 Replace lines 515-524 with user-friendly messages: + + ```typescript + let fallbackText: string; + + if (isContextOverflow) { + fallbackText = + "⚠️ Context overflow — prompt too large for this model. Try a shorter message or a larger-context model."; + } else if (isRoleOrderingError) { + fallbackText = + "⚠️ Message ordering conflict - please try again. If this persists, use /new to start a fresh session."; + } else if (isRateLimit || isOverloaded) { + fallbackText = "The AI service is busy. Please wait a moment and try again."; + } else if (isAuthError) { + fallbackText = + "I couldn't connect to the AI service. Please verify your API key is configured correctly."; + } else if (isBillingError) { + fallbackText = + "I've reached my limit with the AI service. Please check your account balance and try again."; + } else if (isTimeoutError) { + fallbackText = "The request timed out. Please try again, or start a fresh session with /new."; + } else { + fallbackText = + "Something unexpected happened. Try /new to start a fresh conversation, or try again in a moment."; + } + ``` + + #### 4.4 Remove lines 516-519 (old safeMessage/trimmedMessage variables - no longer needed) + +- [ ] **Step 5: Run Lint** + + ```bash + pnpm check + ``` + +- [ ] **Step 6: Run TypeScript** + ```bash + pnpm tsgo + ``` + +--- + +## Phase 3: Test Updates (Steps 7-10) + +- [ ] **Step 7: Edit Test File - Existing Test** + Open: `src/auto-reply/reply.triggers.trigger-handling.includes-error-cause-embedded-agent-throws.e2e.test.ts` + + Update line 69-71: + - From: `"⚠️ Agent failed before reply: sandbox is not defined.\nLogs: openclaw logs --follow"` + - To: `"Something unexpected happened. Try /new to start a fresh conversation, or try again in a moment."` + +- [ ] **Step 8: Add New Tests** + Add these 4 new tests to the same file (after the existing test): + + #### 8.1 Rate Limit Error Test + + ```typescript + it("returns friendly message for rate limit errors", async () => { + await withTempHome(async (home) => { + const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock(); + runEmbeddedPiAgentMock.mockRejectedValue( + new Error("rate_limit_exceeded: API rate limit exceeded"), + ); + + const res = await getReplyFromConfig(BASE_MESSAGE, {}, makeCfg(home)); + const text = Array.isArray(res) ? res[0]?.text : res?.text; + expect(text).toBe("The AI service is busy. Please wait a moment and try again."); + }); + }); + ``` + + #### 8.2 Auth Error Test + + ```typescript + it("returns friendly message for auth errors", async () => { + await withTempHome(async (home) => { + const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock(); + runEmbeddedPiAgentMock.mockRejectedValue(new Error("401 Unauthorized: Invalid API key")); + + const res = await getReplyFromConfig(BASE_MESSAGE, {}, makeCfg(home)); + const text = Array.isArray(res) ? res[0]?.text : res?.text; + expect(text).toBe( + "I couldn't connect to the AI service. Please verify your API key is configured correctly.", + ); + }); + }); + ``` + + #### 8.3 Billing Error Test + + ```typescript + it("returns friendly message for billing errors", async () => { + await withTempHome(async (home) => { + const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock(); + runEmbeddedPiAgentMock.mockRejectedValue( + new Error("402 Payment Required: billing limit exceeded"), + ); + + const res = await getReplyFromConfig(BASE_MESSAGE, {}, makeCfg(home)); + const text = Array.isArray(res) ? res[0]?.text : res?.text; + expect(text).toBe( + "I've reached my limit with the AI service. Please check your account balance and try again.", + ); + }); + }); + ``` + + #### 8.4 Timeout Error Test + + ```typescript + it("returns friendly message for timeout errors", async () => { + await withTempHome(async (home) => { + const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock(); + runEmbeddedPiAgentMock.mockRejectedValue( + new Error("408 Request Timeout: connection timed out"), + ); + + const res = await getReplyFromConfig(BASE_MESSAGE, {}, makeCfg(home)); + const text = Array.isArray(res) ? res[0]?.text : res?.text; + expect(text).toBe( + "The request timed out. Please try again, or start a fresh session with /new.", + ); + }); + }); + ``` + +- [ ] **Step 9: Edit Unit Test** + Open: `src/auto-reply/reply/agent-runner.runreplyagent.test.ts` + + Update line ~740-742: + - Change expected message to: `"Something unexpected happened. Try /new to start a fresh conversation, or try again in a moment."` + +- [ ] **Step 10: Run Tests** + ```bash + pnpm test src/auto-reply/reply/agent-runner.runreplyagent.test.ts + pnpm vitest run --config vitest.e2e.config.ts src/auto-reply/reply.triggers.trigger-handling.includes-error-cause-embedded-agent-throws.e2e.test.ts + ``` + +--- + +## Phase 4: Changelog (Step 11) + +- [ ] **Step 11: Update CHANGELOG.md** + Add to `### Fixes` section under `## 2026.2.19`: + ```markdown + - Auto-reply: show user-friendly error messages based on error type (rate limit, auth, billing, timeout) instead of exposing technical details. + ``` + +--- + +## Phase 5: Commit & Push (Steps 12-14) + +- [ ] **Step 12: Review Changes** + + ```bash + git diff + ``` + +- [ ] **Step 13: Stage and Commit** + + ```bash + git add src/auto-reply/reply/agent-runner-execution.ts \ + src/auto-reply/reply/agent-runner.runreplyagent.test.ts \ + src/auto-reply/reply.triggers.trigger-handling.includes-error-cause-embedded-agent-throws.e2e.test.ts \ + CHANGELOG.md + + scripts/committer "Auto-reply: improve user-facing error messages" \ + src/auto-reply/reply/agent-runner-execution.ts \ + src/auto-reply/reply/agent-runner.runreplyagent.test.ts \ + src/auto-reply/reply.triggers.trigger-handling.includes-error-cause-embedded-agent-throws.e2e.test.ts \ + CHANGELOG.md + ``` + +- [ ] **Step 14: Push** + ```bash + git push -u origin refactor/improve-error-messages + ``` + +--- + +## Phase 6: Create PR (Step 15) + +- [ ] **Step 15: Create Pull Request** + - Use GitHub CLI: `gh pr create` + - Or create via web interface + - Fill out `.github/pull_request_template.md` + +--- + +## Error Type to Message Mapping + +| Error Type | Detection Function | User-Friendly Message | +| ---------------------- | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | +| Context overflow | `isLikelyContextOverflowError()` | "⚠️ Context overflow — prompt too large for this model. Try a shorter message or a larger-context model." | +| Role ordering conflict | `/incorrect role information\|roles must alternate/i` | "⚠️ Message ordering conflict - please try again. If this persists, use /new to start a fresh session." | +| Rate limit | `isRateLimitErrorMessage()` | "The AI service is busy. Please wait a moment and try again." | +| Overloaded | `isOverloadedErrorMessage()` | "The AI service is busy. Please wait a moment and try again." | +| Auth/API key | `isAuthErrorMessage()` | "I couldn't connect to the AI service. Please verify your API key is configured correctly." | +| Billing/quota | `isBillingErrorMessage()` | "I've reached my limit with the AI service. Please check your account balance and try again." | +| Timeout | `isTimeoutErrorMessage()` | "The request timed out. Please try again, or start a fresh session with /new." | +| Unknown/other | fallback | "Something unexpected happened. Try /new to start a fresh conversation, or try again in a moment." | + +--- + +## Files Modified + +| File | Change Type | +| ------------------------------------------------------------------------------------------------------- | ------------------------------- | +| `src/auto-reply/reply/agent-runner-execution.ts` | Add imports, update catch block | +| `src/auto-reply/reply/agent-runner.runreplyagent.test.ts` | Update expected message | +| `src/auto-reply/reply.triggers.trigger-handling.includes-error-cause-embedded-agent-throws.e2e.test.ts` | Update 1 test, add 4 new tests | +| `CHANGELOG.md` | Add Fixes entry | + +--- + +## ⚠️ IMPORTANT: DO NOT COMMIT THIS FILE + +This plan file is for reference only and must NOT be committed. + +When committing, ignore this file (do not stage it): + +```bash +# Ensure this file is NOT staged +git reset docs/plan-user-friendly-error-messages.md +``` From f9af76a243dc160b167be10eac4a0e4fb38c4581 Mon Sep 17 00:00:00 2001 From: Sahil Satralkar <62758655+sahilsatralkar@users.noreply.github.com> Date: Thu, 19 Feb 2026 23:40:55 +0530 Subject: [PATCH 3/4] Revert "docs: include build plan as reference" This reverts commit 2b8a8caf34c022265071884d8b87154cabae1360. --- docs/plan-user-friendly-error-messages.md | 298 ---------------------- 1 file changed, 298 deletions(-) delete mode 100644 docs/plan-user-friendly-error-messages.md diff --git a/docs/plan-user-friendly-error-messages.md b/docs/plan-user-friendly-error-messages.md deleted file mode 100644 index 8c34d622762..00000000000 --- a/docs/plan-user-friendly-error-messages.md +++ /dev/null @@ -1,298 +0,0 @@ -# Execution Plan: Improve User-Friendly Error Messages - -> **IMPORTANT:** This plan file must NOT be committed to the repository. Keep it for reference and PR description. - ---- - -## Phase 1: Preparation (Steps 0-3) - -- [ ] **Step 0: Verify Clean Working Tree** - - ```bash - git status - ``` - - Ensure working tree is clean (no uncommitted changes) before starting. - -- [ ] **Step 1: Run Baseline Tests** - - ```bash - pnpm test src/auto-reply/reply/agent-runner.runreplyagent.test.ts - pnpm vitest run --config vitest.e2e.config.ts src/auto-reply/reply.triggers.trigger-handling.includes-error-cause-embedded-agent-throws.e2e.test.ts - ``` - -- [ ] **Step 2: Verify Branch** - - ```bash - git branch - ``` - - Should show: `refactor/improve-error-messages` - -- [ ] **Step 3: Verify Imports Available** - Confirm these functions exist in `src/agents/pi-embedded-helpers.ts`: - - `isRateLimitErrorMessage()` - - `isAuthErrorMessage()` - - `isBillingErrorMessage()` - - `isTimeoutErrorMessage()` - - `isOverloadedErrorMessage()` - ---- - -## Phase 2: Code Changes (Steps 4-6) - -- [ ] **Step 4: Edit `src/auto-reply/reply/agent-runner-execution.ts`** - - #### 4.1 Add imports (replace lines 7-13): - - ```typescript - import { - isAuthErrorMessage, - isBillingErrorMessage, - isCompactionFailureError, - isContextOverflowError, - isLikelyContextOverflowError, - isOverloadedErrorMessage, - isRateLimitErrorMessage, - isTimeoutErrorMessage, - isTransientHttpError, - sanitizeUserFacingText, - } from "../../agents/pi-embedded-helpers.js"; - ``` - - #### 4.2 Add error classification after line 428: - - ```typescript - // Classify error type for user-friendly messaging - const isRateLimit = isRateLimitErrorMessage(message); - const isAuthError = isAuthErrorMessage(message); - const isBillingError = isBillingErrorMessage(message); - const isTimeoutError = isTimeoutErrorMessage(message); - const isOverloaded = isOverloadedErrorMessage(message); - ``` - - #### 4.3 Replace lines 515-524 with user-friendly messages: - - ```typescript - let fallbackText: string; - - if (isContextOverflow) { - fallbackText = - "⚠️ Context overflow — prompt too large for this model. Try a shorter message or a larger-context model."; - } else if (isRoleOrderingError) { - fallbackText = - "⚠️ Message ordering conflict - please try again. If this persists, use /new to start a fresh session."; - } else if (isRateLimit || isOverloaded) { - fallbackText = "The AI service is busy. Please wait a moment and try again."; - } else if (isAuthError) { - fallbackText = - "I couldn't connect to the AI service. Please verify your API key is configured correctly."; - } else if (isBillingError) { - fallbackText = - "I've reached my limit with the AI service. Please check your account balance and try again."; - } else if (isTimeoutError) { - fallbackText = "The request timed out. Please try again, or start a fresh session with /new."; - } else { - fallbackText = - "Something unexpected happened. Try /new to start a fresh conversation, or try again in a moment."; - } - ``` - - #### 4.4 Remove lines 516-519 (old safeMessage/trimmedMessage variables - no longer needed) - -- [ ] **Step 5: Run Lint** - - ```bash - pnpm check - ``` - -- [ ] **Step 6: Run TypeScript** - ```bash - pnpm tsgo - ``` - ---- - -## Phase 3: Test Updates (Steps 7-10) - -- [ ] **Step 7: Edit Test File - Existing Test** - Open: `src/auto-reply/reply.triggers.trigger-handling.includes-error-cause-embedded-agent-throws.e2e.test.ts` - - Update line 69-71: - - From: `"⚠️ Agent failed before reply: sandbox is not defined.\nLogs: openclaw logs --follow"` - - To: `"Something unexpected happened. Try /new to start a fresh conversation, or try again in a moment."` - -- [ ] **Step 8: Add New Tests** - Add these 4 new tests to the same file (after the existing test): - - #### 8.1 Rate Limit Error Test - - ```typescript - it("returns friendly message for rate limit errors", async () => { - await withTempHome(async (home) => { - const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock(); - runEmbeddedPiAgentMock.mockRejectedValue( - new Error("rate_limit_exceeded: API rate limit exceeded"), - ); - - const res = await getReplyFromConfig(BASE_MESSAGE, {}, makeCfg(home)); - const text = Array.isArray(res) ? res[0]?.text : res?.text; - expect(text).toBe("The AI service is busy. Please wait a moment and try again."); - }); - }); - ``` - - #### 8.2 Auth Error Test - - ```typescript - it("returns friendly message for auth errors", async () => { - await withTempHome(async (home) => { - const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock(); - runEmbeddedPiAgentMock.mockRejectedValue(new Error("401 Unauthorized: Invalid API key")); - - const res = await getReplyFromConfig(BASE_MESSAGE, {}, makeCfg(home)); - const text = Array.isArray(res) ? res[0]?.text : res?.text; - expect(text).toBe( - "I couldn't connect to the AI service. Please verify your API key is configured correctly.", - ); - }); - }); - ``` - - #### 8.3 Billing Error Test - - ```typescript - it("returns friendly message for billing errors", async () => { - await withTempHome(async (home) => { - const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock(); - runEmbeddedPiAgentMock.mockRejectedValue( - new Error("402 Payment Required: billing limit exceeded"), - ); - - const res = await getReplyFromConfig(BASE_MESSAGE, {}, makeCfg(home)); - const text = Array.isArray(res) ? res[0]?.text : res?.text; - expect(text).toBe( - "I've reached my limit with the AI service. Please check your account balance and try again.", - ); - }); - }); - ``` - - #### 8.4 Timeout Error Test - - ```typescript - it("returns friendly message for timeout errors", async () => { - await withTempHome(async (home) => { - const runEmbeddedPiAgentMock = getRunEmbeddedPiAgentMock(); - runEmbeddedPiAgentMock.mockRejectedValue( - new Error("408 Request Timeout: connection timed out"), - ); - - const res = await getReplyFromConfig(BASE_MESSAGE, {}, makeCfg(home)); - const text = Array.isArray(res) ? res[0]?.text : res?.text; - expect(text).toBe( - "The request timed out. Please try again, or start a fresh session with /new.", - ); - }); - }); - ``` - -- [ ] **Step 9: Edit Unit Test** - Open: `src/auto-reply/reply/agent-runner.runreplyagent.test.ts` - - Update line ~740-742: - - Change expected message to: `"Something unexpected happened. Try /new to start a fresh conversation, or try again in a moment."` - -- [ ] **Step 10: Run Tests** - ```bash - pnpm test src/auto-reply/reply/agent-runner.runreplyagent.test.ts - pnpm vitest run --config vitest.e2e.config.ts src/auto-reply/reply.triggers.trigger-handling.includes-error-cause-embedded-agent-throws.e2e.test.ts - ``` - ---- - -## Phase 4: Changelog (Step 11) - -- [ ] **Step 11: Update CHANGELOG.md** - Add to `### Fixes` section under `## 2026.2.19`: - ```markdown - - Auto-reply: show user-friendly error messages based on error type (rate limit, auth, billing, timeout) instead of exposing technical details. - ``` - ---- - -## Phase 5: Commit & Push (Steps 12-14) - -- [ ] **Step 12: Review Changes** - - ```bash - git diff - ``` - -- [ ] **Step 13: Stage and Commit** - - ```bash - git add src/auto-reply/reply/agent-runner-execution.ts \ - src/auto-reply/reply/agent-runner.runreplyagent.test.ts \ - src/auto-reply/reply.triggers.trigger-handling.includes-error-cause-embedded-agent-throws.e2e.test.ts \ - CHANGELOG.md - - scripts/committer "Auto-reply: improve user-facing error messages" \ - src/auto-reply/reply/agent-runner-execution.ts \ - src/auto-reply/reply/agent-runner.runreplyagent.test.ts \ - src/auto-reply/reply.triggers.trigger-handling.includes-error-cause-embedded-agent-throws.e2e.test.ts \ - CHANGELOG.md - ``` - -- [ ] **Step 14: Push** - ```bash - git push -u origin refactor/improve-error-messages - ``` - ---- - -## Phase 6: Create PR (Step 15) - -- [ ] **Step 15: Create Pull Request** - - Use GitHub CLI: `gh pr create` - - Or create via web interface - - Fill out `.github/pull_request_template.md` - ---- - -## Error Type to Message Mapping - -| Error Type | Detection Function | User-Friendly Message | -| ---------------------- | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | -| Context overflow | `isLikelyContextOverflowError()` | "⚠️ Context overflow — prompt too large for this model. Try a shorter message or a larger-context model." | -| Role ordering conflict | `/incorrect role information\|roles must alternate/i` | "⚠️ Message ordering conflict - please try again. If this persists, use /new to start a fresh session." | -| Rate limit | `isRateLimitErrorMessage()` | "The AI service is busy. Please wait a moment and try again." | -| Overloaded | `isOverloadedErrorMessage()` | "The AI service is busy. Please wait a moment and try again." | -| Auth/API key | `isAuthErrorMessage()` | "I couldn't connect to the AI service. Please verify your API key is configured correctly." | -| Billing/quota | `isBillingErrorMessage()` | "I've reached my limit with the AI service. Please check your account balance and try again." | -| Timeout | `isTimeoutErrorMessage()` | "The request timed out. Please try again, or start a fresh session with /new." | -| Unknown/other | fallback | "Something unexpected happened. Try /new to start a fresh conversation, or try again in a moment." | - ---- - -## Files Modified - -| File | Change Type | -| ------------------------------------------------------------------------------------------------------- | ------------------------------- | -| `src/auto-reply/reply/agent-runner-execution.ts` | Add imports, update catch block | -| `src/auto-reply/reply/agent-runner.runreplyagent.test.ts` | Update expected message | -| `src/auto-reply/reply.triggers.trigger-handling.includes-error-cause-embedded-agent-throws.e2e.test.ts` | Update 1 test, add 4 new tests | -| `CHANGELOG.md` | Add Fixes entry | - ---- - -## ⚠️ IMPORTANT: DO NOT COMMIT THIS FILE - -This plan file is for reference only and must NOT be committed. - -When committing, ignore this file (do not stage it): - -```bash -# Ensure this file is NOT staged -git reset docs/plan-user-friendly-error-messages.md -``` From 2f68334cff6a144703c2b2be80db343f0efe2c1a Mon Sep 17 00:00:00 2001 From: Sahil Satralkar <62758655+sahilsatralkar@users.noreply.github.com> Date: Mon, 16 Mar 2026 23:31:21 +0530 Subject: [PATCH 4/4] fix(auto-reply): scope rebased error-message refresh --- CHANGELOG.md | 1 - src/auto-reply/reply/agent-runner-execution.ts | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78ee8ec31fb..5673d2dd5f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2192,7 +2192,6 @@ Docs: https://docs.openclaw.ai - Security/Exec: block grep safe-bin positional operand bypass by setting grep positional budget to zero, so `-e/--regexp` cannot smuggle bare filename reads (for example `.env`) via ambiguous positionals; safe-bin grep patterns must come from `-e/--regexp`. Thanks @athuljayaram for reporting. - Security/Gateway/Agents: remove implicit admin scopes from agent tool gateway calls by classifying methods to least-privilege operator scopes, and enforce owner-only tooling (`cron`, `gateway`, `whatsapp_login`) through centralized tool-policy wrappers plus tool metadata to prevent non-owner DM privilege escalation. Ships in the next npm release. Thanks @Adam55A-code for reporting. - Security/Gateway: centralize gateway method-scope authorization and default non-CLI gateway callers to least-privilege method scopes, with explicit CLI scope handling, full core-handler scope classification coverage, and regression guards to prevent scope drift. -- Auto-reply: show user-friendly error messages based on error type (rate limit, auth, billing, timeout) instead of exposing technical details. - Security/Net: block SSRF bypass via NAT64 (`64:ff9b::/96`, `64:ff9b:1::/48`), 6to4 (`2002::/16`), and Teredo (`2001:0000::/32`) IPv6 transition addresses, and fail closed on IPv6 parse errors. Thanks @jackhax. - Security/OTEL: sanitize OTLP endpoint URL resolution. (#13791) Thanks @vincentkoc. - Security: patch Dependabot security issues in pnpm lock. (#20832) Thanks @vincentkoc. diff --git a/src/auto-reply/reply/agent-runner-execution.ts b/src/auto-reply/reply/agent-runner-execution.ts index 7ca57369e50..c1e5ed90e4e 100644 --- a/src/auto-reply/reply/agent-runner-execution.ts +++ b/src/auto-reply/reply/agent-runner-execution.ts @@ -13,6 +13,7 @@ import { isLikelyContextOverflowError, isOverloadedErrorMessage, isRateLimitErrorMessage, + sanitizeUserFacingText, isTimeoutErrorMessage, isTransientHttpError, } from "../../agents/pi-embedded-helpers.js";