Skip to content

fix(ui): i18n locale before render + fix: tool_use mismatch suggest /new (#46366, #46365)#46373

Open
xuwei-xy wants to merge 1 commit intoopenclaw:mainfrom
xuwei-xy:batch5-pr46366-pr46365-v2
Open

fix(ui): i18n locale before render + fix: tool_use mismatch suggest /new (#46366, #46365)#46373
xuwei-xy wants to merge 1 commit intoopenclaw:mainfrom
xuwei-xy:batch5-pr46366-pr46365-v2

Conversation

@xuwei-xy
Copy link
Copy Markdown

@openclaw-barnacle openclaw-barnacle bot added app: web-ui App: web-ui agents Agent runtime and tooling size: S labels Mar 14, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 14, 2026

Greptile Summary

This PR bundles two bug fixes: (1) it detects tool_use/tool_result history-corruption errors from the Anthropic API and surfaces a friendly recovery message directing users to /new, and (2) it ensures the i18n locale is fully loaded before the first render of the Lit application component.

Key changes:

  • errors.ts: Adds isToolUseResultMismatchError / TOOL_USE_RESULT_MISMATCH_RE and wires it into both formatAssistantErrorText and sanitizeUserFacingText (behind the existing errorContext guard).
  • translate.ts: Makes loadLocale async and exposes i18n.ready: Promise<void> so callers can await initial locale resolution.
  • app.ts: Introduces a localeReady state flag; the component renders nothing until both i18n.ready and any explicit setLocale call from app settings have settled, preventing a flash of untranslated UI.
  • Tests are added or updated for all three areas.

Issues found:

  • The first alternative of TOOL_USE_RESULT_MISMATCH_RE is just tool_use_id, which matches any string containing that substring. Unrelated validation errors (e.g. "Invalid format for tool_use_id", "tool_use_id is required") will incorrectly display "Conversation history corruption detected" — misleading advice that may prompt users to discard their session unnecessarily.
  • In app.ts, i18n.ready (reads from localStorage/navigator) and settingsLocale (reads from app settings) are run concurrently via Promise.all. If the two locale sources disagree, the non-deterministic resolution order of setLocale means the final rendered locale could come from either source.

Confidence Score: 3/5

  • Mostly safe to merge; two non-critical issues should be addressed before landing.
  • The i18n fix is clean and the test improvement is strictly better. The tool-mismatch fix solves a real user-facing problem. However, the overly broad tool_use_id regex alternative can cause unrelated validation errors to display an incorrect "history corruption" message, and the concurrent Promise.all locale-loading pattern introduces a low-probability but silent race condition when localStorage and app settings disagree on the active locale.
  • src/agents/pi-embedded-helpers/errors.ts (regex breadth) and ui/src/ui/app.ts (concurrent locale loading).
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/agents/pi-embedded-helpers/errors.ts
Line: 838-839

Comment:
**Overly broad `tool_use_id` alternative in regex**

The first branch of the regex — `tool_use_id` — matches any string that merely contains the substring `"tool_use_id"`, with no further constraints. This means error messages completely unrelated to a history-corruption mismatch (e.g. `"Invalid format for tool_use_id"`, `"tool_use_id is required"`, or `"Unsupported tool_use_id type"`) will all return `"Conversation history corruption detected. Please use /new to start a fresh session."`, which is incorrect advice and will confuse users.

The other two alternatives (`tool_result.*corresponding.*tool_use` and `unexpected.*tool.*block`) are adequately specific. The first alternative should be tightened to require additional context, for example:

```suggestion
const TOOL_USE_RESULT_MISMATCH_RE =
  /tool_result.*tool_use_id.*not found|tool_result.*corresponding.*tool_use|unexpected.*tool.*block/i;
```

This matches the actual Anthropic API error strings exercised by the tests (`"tool_result block with tool_use_id … not found in the immediately preceding assistant message"`) while avoiding false positives on generic validation errors that happen to reference the field name.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: ui/src/ui/app.ts
Line: 119-133

Comment:
**Potential race condition between `i18n.ready` and `settingsLocale`**

Both `i18n.ready` (which loads the locale persisted in `localStorage` / derived from `navigator.language`) and `settingsLocale` (which loads the locale from app `settings`) are kicked off concurrently via `Promise.all`. Because `setLocale` is not re-entrant, whichever resolves *last* wins and sets `this.locale`.

If the two sources disagree — e.g. `localStorage` says `"zh-CN"` and `settings.locale` says `"fr"` — the final rendered locale will be whichever translation finishes loading later, which is non-deterministic. App settings should be the authoritative source, so `settingsLocale` ought to be applied *after* `i18n.ready` resolves to avoid being overwritten:

```
i18n.ready
  .then(() => isSupportedLocale(this.settings.locale) ? i18n.setLocale(this.settings.locale) : Promise.resolve())
  .then(() => { this.localeReady = true; }, () => { this.localeReady = true; });
```

This is a low-probability edge case (requires the two stores to diverge), but it would be an invisible, intermittent bug.

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: 55fa530

Comment on lines +838 to +839
const TOOL_USE_RESULT_MISMATCH_RE =
/tool_use_id|tool_result.*corresponding.*tool_use|unexpected.*tool.*block/i;
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.

Overly broad tool_use_id alternative in regex

The first branch of the regex — tool_use_id — matches any string that merely contains the substring "tool_use_id", with no further constraints. This means error messages completely unrelated to a history-corruption mismatch (e.g. "Invalid format for tool_use_id", "tool_use_id is required", or "Unsupported tool_use_id type") will all return "Conversation history corruption detected. Please use /new to start a fresh session.", which is incorrect advice and will confuse users.

The other two alternatives (tool_result.*corresponding.*tool_use and unexpected.*tool.*block) are adequately specific. The first alternative should be tightened to require additional context, for example:

Suggested change
const TOOL_USE_RESULT_MISMATCH_RE =
/tool_use_id|tool_result.*corresponding.*tool_use|unexpected.*tool.*block/i;
const TOOL_USE_RESULT_MISMATCH_RE =
/tool_result.*tool_use_id.*not found|tool_result.*corresponding.*tool_use|unexpected.*tool.*block/i;

This matches the actual Anthropic API error strings exercised by the tests ("tool_result block with tool_use_id … not found in the immediately preceding assistant message") while avoiding false positives on generic validation errors that happen to reference the field name.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/pi-embedded-helpers/errors.ts
Line: 838-839

Comment:
**Overly broad `tool_use_id` alternative in regex**

The first branch of the regex — `tool_use_id` — matches any string that merely contains the substring `"tool_use_id"`, with no further constraints. This means error messages completely unrelated to a history-corruption mismatch (e.g. `"Invalid format for tool_use_id"`, `"tool_use_id is required"`, or `"Unsupported tool_use_id type"`) will all return `"Conversation history corruption detected. Please use /new to start a fresh session."`, which is incorrect advice and will confuse users.

The other two alternatives (`tool_result.*corresponding.*tool_use` and `unexpected.*tool.*block`) are adequately specific. The first alternative should be tightened to require additional context, for example:

```suggestion
const TOOL_USE_RESULT_MISMATCH_RE =
  /tool_result.*tool_use_id.*not found|tool_result.*corresponding.*tool_use|unexpected.*tool.*block/i;
```

This matches the actual Anthropic API error strings exercised by the tests (`"tool_result block with tool_use_id … not found in the immediately preceding assistant message"`) while avoiding false positives on generic validation errors that happen to reference the field name.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines 119 to 133
constructor() {
super();
if (isSupportedLocale(this.settings.locale)) {
void i18n.setLocale(this.settings.locale);
}
const settingsLocale = isSupportedLocale(this.settings.locale)
? i18n.setLocale(this.settings.locale)
: Promise.resolve();
Promise.all([i18n.ready, settingsLocale]).then(
() => {
this.localeReady = true;
},
() => {
// Locale load failed; render with the default locale.
this.localeReady = true;
},
);
}
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.

Potential race condition between i18n.ready and settingsLocale

Both i18n.ready (which loads the locale persisted in localStorage / derived from navigator.language) and settingsLocale (which loads the locale from app settings) are kicked off concurrently via Promise.all. Because setLocale is not re-entrant, whichever resolves last wins and sets this.locale.

If the two sources disagree — e.g. localStorage says "zh-CN" and settings.locale says "fr" — the final rendered locale will be whichever translation finishes loading later, which is non-deterministic. App settings should be the authoritative source, so settingsLocale ought to be applied after i18n.ready resolves to avoid being overwritten:

i18n.ready
  .then(() => isSupportedLocale(this.settings.locale) ? i18n.setLocale(this.settings.locale) : Promise.resolve())
  .then(() => { this.localeReady = true; }, () => { this.localeReady = true; });

This is a low-probability edge case (requires the two stores to diverge), but it would be an invisible, intermittent bug.

Prompt To Fix With AI
This is a comment left during a code review.
Path: ui/src/ui/app.ts
Line: 119-133

Comment:
**Potential race condition between `i18n.ready` and `settingsLocale`**

Both `i18n.ready` (which loads the locale persisted in `localStorage` / derived from `navigator.language`) and `settingsLocale` (which loads the locale from app `settings`) are kicked off concurrently via `Promise.all`. Because `setLocale` is not re-entrant, whichever resolves *last* wins and sets `this.locale`.

If the two sources disagree — e.g. `localStorage` says `"zh-CN"` and `settings.locale` says `"fr"` — the final rendered locale will be whichever translation finishes loading later, which is non-deterministic. App settings should be the authoritative source, so `settingsLocale` ought to be applied *after* `i18n.ready` resolves to avoid being overwritten:

```
i18n.ready
  .then(() => isSupportedLocale(this.settings.locale) ? i18n.setLocale(this.settings.locale) : Promise.resolve())
  .then(() => { this.localeReady = true; }, () => { this.localeReady = true; });
```

This is a low-probability edge case (requires the two stores to diverge), but it would be an invisible, intermittent bug.

How can I resolve this? If you propose a fix, please make it concise.

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

Labels

agents Agent runtime and tooling app: web-ui App: web-ui size: S

Projects

None yet

1 participant