Skip to content

fix(agents): suppress unrecognized errors from user surface#41803

Open
carrotRakko wants to merge 3 commits intoopenclaw:mainfrom
delight-co:fix/suppress-unrecognized-error-fallback
Open

fix(agents): suppress unrecognized errors from user surface#41803
carrotRakko wants to merge 3 commits intoopenclaw:mainfrom
delight-co:fix/suppress-unrecognized-error-fallback

Conversation

@carrotRakko
Copy link
Copy Markdown
Contributor

@carrotRakko carrotRakko commented Mar 10, 2026

Summary

  • Problem: formatAssistantErrorText falls through to a final branch that returns raw, unrecognized error strings (truncated at 600 chars) directly to the chat surface. This leaks API internals, confuses users, and spams channels.
  • Why it matters: Orphaned tool_result errors, rate limits, and other unexpected API responses become visible to end users in Telegram/Slack/etc.
  • What changed: The final fallback now logs the full error for debugging and returns a safe generic message: "Something went wrong. Please try again, or use /new to start a fresh session."
  • What did NOT change: All recognized error branches (overloaded, rate_limit, too_many_tokens, etc.) are untouched — only the unrecognized catch-all is affected.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

User-visible / Behavior Changes

Unrecognized errors that previously leaked raw API messages to the chat surface now show a generic user-friendly message. The raw error is still logged at warn level for debugging.

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Repro + Verification

Environment

  • OS: Linux (Amazon Linux 2023, aarch64)
  • Runtime/container: Node.js
  • Model/provider: claude-opus-4-5 via Anthropic
  • Integration/channel: Telegram group
  • Relevant config: Default

Steps

  1. Trigger a session with a corrupted transcript (orphaned tool_result without matching tool_use)
  2. Send any message to the agent

Expected

  • User sees a generic error message, not raw API internals.

Actual

  • Before: Raw error string (up to 600 chars) sent to chat surface.
  • After: Generic message shown; full error logged at warn level.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

New test in formatassistanterrortext.test.ts verifies the unrecognized-error branch returns the generic message. Lifecycle test expectations updated.

Human Verification (required)

  • Verified scenarios: Tested on a fork with Telegram and Slack. Orphaned tool_result errors now show generic message instead of raw API error.
  • Edge cases checked: All recognized error patterns (overloaded, rate_limit, too_many_tokens, etc.) still return their specific messages — only the catch-all is changed.
  • What you did not verify: Exhaustive list of all possible unrecognized error strings.

Review Conversations

N/A — fresh PR, no review conversations yet.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No

Failure Recovery (if this breaks)

  • How to disable/revert: Revert changes in errors.ts to restore the raw-truncation fallback.
  • Files/config to restore: src/agents/pi-embedded-helpers/errors.ts
  • Known bad symptoms: If a new recognized error pattern is added upstream that should have a specific message, it would be caught by this generic fallback instead. This is safe but suboptimal — the specific branch should be added.

Risks and Mitigations

  • Risk: Masking errors that operators need to see.
    • Mitigation: Full error text is logged at warn level. Operators can see it in logs.

✍️ Author: Claude Code with @carrotRakko (AI-written, human-approved)

@openclaw-barnacle openclaw-barnacle bot added agents Agent runtime and tooling size: XS labels Mar 10, 2026
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: 84692257ff

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 10, 2026

Greptile Summary

This PR successfully fixes a bug where formatAssistantErrorText's final fallback branch was returning raw, potentially internal error strings (truncated at 600 chars) directly to the user-facing chat surface.

What Changed:

  • errors.ts: The final fallback now logs the raw error at warn level (up to 500 chars) for operator debugging and returns a safe, friendly generic message ("Something went wrong. Please try again, or use /new to start a fresh session.")
  • formatassistanterrortext.test.ts: Two new unit tests verify unrecognized errors (both realistic orphaned tool_result case and long 800-char strings) return the generic message without leaking raw content
  • pi-embedded-subscribe.handlers.lifecycle.test.ts: Two test expectations updated to reflect the generic user message in the error field; the separately-tracked rawErrorPreview (redacted via buildApiErrorObservationFields) remains unchanged

Impact:

  • All recognized error patterns (overloaded, rate_limit, context_overflow, etc.) are unchanged
  • Only the unrecognized catch-all path is affected
  • Raw error text is available to operators in logs for debugging
  • Users see a friendly, generic message instead of leaked API internals

Confidence Score: 5/5

  • This PR is safe to merge — it's a straightforward bug fix with good test coverage that eliminates information leakage without affecting recognized error handling paths.
  • The change is minimal and focused: the final fallback in formatAssistantErrorText now suppresses raw errors from users and returns a safe generic message instead. All recognized error patterns are explicitly untouched. The fix is well-tested with concrete test cases covering realistic orphaned error scenarios and edge cases (long error strings). No comments require action.
  • No files require special attention

Last reviewed commit: 8469225

The final fallback in formatAssistantErrorText returned raw error text
to the user when no known error pattern matched. This leaked internal
API error details (e.g. orphaned tool call errors, provider-specific
diagnostics) to messaging surfaces.

Replace the fallback with a generic user-safe message and log the
original error for debugging.

Related openclaw#11038, openclaw#16948

✍️ Author: Claude Code with @carrotRakko (AI-written, human-approved)
@delight-ai-agent delight-ai-agent force-pushed the fix/suppress-unrecognized-error-fallback branch from 8469225 to f50f9b5 Compare March 22, 2026 13:38
@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot bot commented Mar 22, 2026

🔒 Aisle Security Analysis

We found 2 potential security issue(s) in this PR:

# Severity Title
1 🟠 High Sensitive data exposure in logs due to truncation before redaction in formatAssistantErrorText
2 🔵 Low Terminal/log injection via unsanitized control characters in suppressed error preview log

1. 🟠 Sensitive data exposure in logs due to truncation before redaction in formatAssistantErrorText

Property Value
Severity High
CWE CWE-532
Location src/agents/pi-embedded-helpers/errors.ts:655-658

Description

formatAssistantErrorText() suppresses unrecognized errors from the user, but logs a preview derived by truncating the raw error before applying redactSensitiveText.

This can leak secrets into logs:

  • Input: raw originates from msg.errorMessage (provider/SDK/tooling error strings), which may include credentials (e.g., Bearer ..., API keys, tokens).
  • The code truncates: raw.slice(0, 500).
  • Redaction is then applied to the truncated string.
  • Several default redact patterns require an end word-boundary (e.g., \bBearer ...\b, \b(sk-...)\b). If a token spans across the 500-char boundary, the truncated preview may not match these patterns, leaving a partial (but still sensitive) token unredacted.
  • Sink: log.warn(...) writes the preview to log sinks (console/file), resulting in potential credential leakage.

Vulnerable code:

const preview = redactSensitiveText(raw.slice(0, 500)).replace(/[\r\n]+/g, " ");
log.warn(`Unrecognized error suppressed from user surface: ${preview}`);

Why truncation can bypass redaction (examples from default patterns in src/logging/redact.ts):

  • \bBearer\s+([A-Za-z0-9._\-+=]{18,})\b
  • \b(sk-[A-Za-z0-9_-]{8,})\b

If the token is cut mid-string, the trailing \b may not match, and the token fragment is logged.

Recommendation

Redact before truncation (or truncate with sufficient overlap, then redact, then truncate again) so boundary-dependent patterns still match.

Safer approach (redact full text, then truncate the redacted result):

const redacted = redactSensitiveText(raw).replace(/[\r\n]+/g, " ");
const preview = redacted.length > 500 ? `${redacted.slice(0, 500)}…` : redacted;
log.warn(`Unrecognized error suppressed from user surface: ${preview}`);

If performance is a concern for very large errors, redact a larger slice with overlap, then truncate:

const window = raw.slice(0, 1500); // larger than preview
const redactedWindow = redactSensitiveText(window).replace(/[\r\n]+/g, " ");
const preview = redactedWindow.length > 500 ? `${redactedWindow.slice(0, 500)}…` : redactedWindow;

Optionally, consider fully replacing matched secrets with *** (rather than keeping start/end) for log outputs to reduce residual disclosure risk.


2. 🔵 Terminal/log injection via unsanitized control characters in suppressed error preview log

Property Value
Severity Low
CWE CWE-117
Location src/agents/pi-embedded-helpers/errors.ts:655-659

Description

formatAssistantErrorText() logs a preview of unrecognized raw error text after only redacting secrets and replacing CR/LF. Other control characters (e.g., \t, \u001b / ANSI escape sequences, DEL) can still flow into console logs.

  • Input: raw originates from assistant/provider error payloads and can include attacker-influenced content (e.g., upstream service errors, tool output propagated into errors)
  • Transformation: redactSensitiveText(...) masks secrets but does not neutralize control characters
  • Sink: log.warn(...) ultimately writes to console in pretty/compact styles without stripping ANSI/control chars, enabling terminal escape injection / log obfuscation (CWE-117)

Vulnerable code:

const preview = redactSensitiveText(raw.slice(0, 500)).replace(/[\r\n]+/g, " ");
log.warn(`Unrecognized error suppressed from user surface: ${preview}`);

Recommendation

Sanitize the preview for logs by stripping ANSI escape sequences and all C0 control characters (not just CR/LF) before logging.

This repo already provides sanitizeForLog() in src/terminal/ansi.ts; use it here (or enforce sanitization centrally in the logger):

import { sanitizeForLog } from "../../terminal/ansi.js";

const preview = sanitizeForLog(redactSensitiveText(raw.slice(0, 500)))
  .replace(/\s+/g, " ")
  .trim();
log.warn(`Unrecognized error suppressed from user surface: ${preview}`);

If you want to preserve some whitespace, normalize it (e.g., collapse to single spaces) rather than allowing raw tabs/escapes.


Analyzed PR: #41803 at commit 80307d2

Last updated on: 2026-03-22T14:39:45Z

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: f50f9b5ab4

ℹ️ 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".

Address review feedback:
- Add auth and model-not-found checks before the generic catch-all
  so actionable guidance survives retry exhaustion (Codex P2)
- Redact sensitive text (API keys, tokens) in log preview using
  existing redactSensitiveText utility (Aisle High CWE-532)
- Strip CR/LF from log preview to prevent log forging (Aisle Low CWE-117)

✍️ Author: Claude Code with @carrotRakko (AI-written, human-approved)
@carrotRakko

This comment was marked as spam.

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: 73aaef6863

ℹ️ 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".

- Check isCliSessionExpiredErrorMessage before auth (bare "expired"
  in auth patterns would misclassify "session expired" as auth failure)
- Check isImageDimensionErrorMessage before catch-all to preserve
  actionable resize guidance instead of generic "Something went wrong"

✍️ Author: Claude Code with @carrotRakko (AI-written, human-approved)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling size: XS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tool call result delivery fails after session compaction — error leaks to user chat Context corruption exposes raw API errors to chat surface

1 participant