Skip to content

fix(webui-v2): render chat errors inline - #5822

Merged
think-in-universe merged 21 commits into
mainfrom
issue-5708-inline-chat-errors
Jul 10, 2026
Merged

think-in-universe merged 21 commits into
mainfrom
issue-5708-inline-chat-errors

Conversation

@italic-jinxin

@italic-jinxin italic-jinxin commented Jul 8, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Render WebChat v2 send/create-thread request failures as inline error messages in the chat stream.
  • Handle redacted WebChat v2 SSE error frames in the chat event reducer and dedupe reconnect repeats.
  • Remove the matching request-error bubble when retrying a failed user message.
  • Add focused unit coverage plus a Playwright regression for failed send rendering.
image

Linked Issue

Closes #5708

Validation

  • node --test crates/ironclaw_webui_v2/static/js/pages/chat/lib/failureMessages.test.mjs
  • node --test crates/ironclaw_webui_v2/static/js/pages/chat/lib/useChatEvents.test.mjs
  • node --test crates/ironclaw_webui_v2/static/js/pages/chat/lib/useChat-send.test.mjs
  • NO_PROXY=127.0.0.1,localhost no_proxy=127.0.0.1,localhost tests/e2e/.venv/bin/pytest tests/e2e/scenarios/test_reborn_webui_v2_legacy_pending_messages.py::test_reborn_legacy_send_failure_renders_inline_error_not_toast -q
  • git diff --check

Security Impact

No auth, secret, sandbox, listener, or outbound-network behavior changes.

Database Impact

No database or migration changes.

Blast Radius

Limited to WebChat v2 frontend chat error rendering and one E2E coverage entry.

Rollback Plan

Revert this PR; send/stream failures return to the previous failed-bubble/stream-error behavior.


Review track: C

@italic-jinxin italic-jinxin added size: L 200-499 changed lines risk: medium Business logic, config, or moderate-risk modules contributor: core 20+ merged PRs labels Jul 8, 2026
@ironloopai

ironloopai Bot commented Jul 8, 2026 •

Copy link
Copy Markdown
Contributor

🔎 IronLoop Review Status

Head: 1a8df2788157c6dd924f0480b0c9dacfd21835a6
Result: No reviewer jobs are scheduled yet.
Next: Run @ironloopai review to start reviewers.
Updated: 2026-07-10T04:57:05.066Z

Current reviewers:

Reviewer State Verdict Findings Last update
none Queued N/A No reviewer jobs scheduled yet. N/A
Reviewer summaries
Reviewer Detail
none No reviewer jobs scheduled yet.
Recent activity
Time Reviewer State Detail
N/A N/A Waiting No progress events recorded yet.
Available commands
  • @ironloopai help
  • @ironloopai agents
  • @ironloopai review
  • @ironloopai review --agent <agent>
  • @ironloopai status
Run metadata

Admission: webhook accepted the request and IronLoop persisted reviewer state before this projection.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5822 July 8, 2026 11:00 Destroyed
@coderabbitai

coderabbitai Bot commented Jul 8, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 249df35b-8d20-4c82-8587-64e87b5cb061

📥 Commits

Reviewing files that changed from the base of the PR and between 034d418 and 1a8df27.

📒 Files selected for processing (2)
  • crates/ironclaw_webui_v2/frontend/src/pages/chat/components/message-bubble.test.ts
  • crates/ironclaw_webui_v2/frontend/src/pages/chat/components/message-bubble.tsx
💤 Files with no reviewable changes (2)
  • crates/ironclaw_webui_v2/frontend/src/pages/chat/components/message-bubble.test.ts
  • crates/ironclaw_webui_v2/frontend/src/pages/chat/components/message-bubble.tsx

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Chat request and streaming failures now appear as inline error messages instead of toast notifications.
    • Error messages provide clearer, safer text and redact potentially sensitive credentials.
    • Retrying a failed message now removes related failure details and restores a clean conversation state.
    • Duplicate run and stream errors are suppressed to reduce clutter.
    • Stream failures now correctly clear processing state, allowing chats to continue.
  • Tests

    • Added coverage for inline errors, retries, error formatting, deduplication, and failure-state recovery.

Walkthrough

Chat error handling now uses centralized roles and typed failure messages. Request and stream failures are appended inline with deterministic IDs, retries remove linked failure bubbles, and error-role bubbles use left-aligned chat-stream layout. Unit, rendering, runtime, and E2E coverage were updated.

Changes

Inline error message rendering

Layer / File(s) Summary
Chat message and failure contracts
crates/ironclaw_webui_v2/frontend/src/pages/chat/lib/message-types.ts, crates/ironclaw_webui_v2/frontend/src/pages/chat/lib/failureMessages.ts, crates/ironclaw_webui_v2/frontend/src/pages/chat/lib/*test.ts
Centralized roles, typed error messages, deterministic failure IDs, credential redaction, and request/stream failure formatting are added and tested.
Send, retry, and admission handling
crates/ironclaw_webui_v2/frontend/src/pages/chat/hooks/useChat.ts, crates/ironclaw_webui_v2/frontend/src/pages/chat/lib/useChat-send.test.ts
Request failures append linked inline error bubbles, failed optimistic messages retain formatted error content, retries remove related failures, and stream-error admission state is reset.
Stream error handling and run-failure deduplication
crates/ironclaw_webui_v2/frontend/src/pages/chat/lib/useChatEvents.ts, crates/ironclaw_webui_v2/frontend/src/pages/chat/lib/useChatEvents.test.ts
SSE error frames clear active state and append collision-safe stream failures; adjacent run failures are deduplicated and unknown failures can be promoted to known run failures.
Role-based inline bubble rendering
crates/ironclaw_webui_v2/frontend/src/pages/chat/components/message-bubble.tsx, crates/ironclaw_webui_v2/frontend/src/pages/chat/components/message-bubble.test.ts, crates/ironclaw_webui_v2/src/static_assets/assets.rs
MessageBubble role checks and layout classes use shared constants, with ERROR messages rendered left-aligned and validated through markup and asset assertions.
End-to-end inline failure coverage
tests/e2e/scenarios/test_reborn_webui_v2_legacy_pending_messages.py, tests/e2e/reborn_coverage_tests.txt
A mocked HTTP 503 send failure is verified as an inline error with constrained layout and no toast container.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested reviewers: think-in-universe

Sequence Diagram(s)

sequenceDiagram
  participant Chat
  participant useChat
  participant useChatEvents
  participant ThreadStore
  participant MessageBubble

  Chat->>useChat: send message
  useChat->>ThreadStore: add optimistic user message
  useChatEvents-->>useChat: report stream error
  useChat->>ThreadStore: append inline error message
  ThreadStore->>MessageBubble: render error-role message
Loading
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Conventional-commit style title matches the main change: inline chat error rendering for webui-v2.
Description check ✅ Passed The description covers summary, linked issue, validation, impact, rollback, and review track, but omits the Change Type checklist.
Linked Issues check ✅ Passed The change renders request and stream errors inline, dedupes repeats, and removes retry-linked error bubbles, matching #5708.
Out of Scope Changes check ✅ Passed No clear unrelated code paths are introduced; the added tests and E2E coverage support the inline-error fix.

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.

@github-actions github-actions Bot added risk: low Changes to docs, tests, or low-risk modules and removed risk: medium Business logic, config, or moderate-risk modules labels Jul 8, 2026
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5822 July 8, 2026 11:01 Destroyed

@gemini-code-assist gemini-code-assist Bot left a comment

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.

Code Review

This pull request introduces inline error message rendering for request and stream failures in the chat UI, replacing toast notifications. It adds error handling to thread creation and message sending, formats stream and request errors, and updates unit and E2E tests to cover these new failure scenarios. Feedback is provided regarding the stream error deduplication logic, where using a static ID derived solely from error properties can cause subsequent stream errors of the same type to be silently ignored.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@github-actions

github-actions Bot commented Jul 8, 2026 •

Copy link
Copy Markdown
Contributor

Coverage ratchet

Ratchet mode: ENFORCING

RATCHET PASS: global
  observed: 85.07% (285099 / 335144 lines)
  floor:    85.3% (tolerance 0.5pp -> effective floor 84.8%)
  denominator: 335144 lines now vs 320188 at floor capture (+14956 lines, +4.67%) — not a material change

⚠️ 2 Reborn crate(s) have 0 int-tier coverage (target: 0) — ironclaw_prompt_envelope, ironclaw_scripts

Reborn integration-tier coverage

Line coverage (Reborn crates): 85.07% — 285099 / 335144 lines

Per-crate breakdown (63 crates, lowest-covered first)
Crate Line % Covered / Total
ironclaw_prompt_envelope 0% 0 / 88
ironclaw_scripts 0% 0 / 345
ironclaw_runtime_policy 31.75% 80 / 252
ironclaw_event_projections 43.31% 673 / 1554
ironclaw_run_state 52.36% 222 / 424
ironclaw_authorization 53.66% 462 / 861
ironclaw_triggers 60.05% 1759 / 2929
ironclaw_observability 61.54% 16 / 26
ironclaw_reborn_cli 62.51% 3763 / 6020
ironclaw_webui_v2 62.62% 2632 / 4203
ironclaw_mcp 63.03% 578 / 917
ironclaw_reborn_migration 66.93% 1168 / 1745
ironclaw_dispatcher 67.15% 92 / 137
ironclaw_filesystem 67.25% 3833 / 5700
ironclaw_memory 69.2% 773 / 1117
ironclaw_trust 72.88% 661 / 907
ironclaw_capabilities 74.08% 1658 / 2238
ironclaw_wasm_limiter 74.6% 47 / 63
ironclaw_reborn_event_store 74.61% 958 / 1284
ironclaw_extractors 74.72% 538 / 720
ironclaw_first_party_extensions 77.66% 5400 / 6953
ironclaw_llm 78.16% 20068 / 25677
ironclaw_product_context 78.57% 11 / 14
ironclaw_process_sandbox 80.65% 671 / 832
ironclaw_wasm_product_adapters 80.71% 1448 / 1794
ironclaw_reborn_openai_compat 80.95% 956 / 1181
ironclaw_memory_native 81.22% 3205 / 3946
ironclaw_secrets 82.7% 2791 / 3375
ironclaw_wasm 82.72% 996 / 1204
ironclaw_events 82.84% 1762 / 2127
ironclaw_turns 84.04% 13325 / 15856
ironclaw_processes 84.06% 965 / 1148
ironclaw_auth 84.08% 3068 / 3649
ironclaw_reborn_config 84.33% 1814 / 2151
ironclaw_host_api 84.8% 2589 / 3053
ironclaw_product_workflow 85.26% 10839 / 12713
ironclaw_threads 85.82% 4188 / 4880
ironclaw_projects 85.92% 659 / 767
ironclaw_network 86.12% 670 / 778
ironclaw_common 86.46% 1514 / 1751
ironclaw_slack_v2_adapter 86.79% 1806 / 2081
ironclaw_product_adapters 86.98% 3207 / 3687
ironclaw_reborn_identity 87.03% 557 / 640
ironclaw_skills 87.58% 4470 / 5104
ironclaw_hooks 87.77% 9914 / 11296
ironclaw_product_adapter_registry 88.06% 531 / 603
ironclaw_reborn_traces 88.19% 11946 / 13546
ironclaw_extensions 88.33% 2633 / 2981
ironclaw_reborn_composition 88.71% 69912 / 78808
ironclaw_host_runtime 88.9% 17707 / 19917
ironclaw_reborn 89.1% 15970 / 17923
ironclaw_approvals 89.24% 1584 / 1775
ironclaw_conversations 90% 2925 / 3250
ironclaw_event_streams 90.82% 1009 / 1111
ironclaw_loop_support 92.47% 14702 / 15899
ironclaw_resources 92.81% 4722 / 5088
ironclaw_attachments 93.06% 630 / 677
ironclaw_reborn_webui_ingress 93.19% 2217 / 2379
ironclaw_telegram_v2_adapter 93.87% 2452 / 2612
ironclaw_agent_loop 94.58% 8786 / 9289
ironclaw_safety 94.8% 3668 / 3869
ironclaw_first_party_extension_ports 95.24% 3343 / 3510
ironclaw_outbound 95.59% 3556 / 3720

This table itself is informational and never gates the PR on its own — not the percentage, not the per-crate holes, not the 0-coverage callout. A separate coverage ratchet (dry-run until enforce=true; see tests/integration/coverage-floor.toml) can fail the build on specific configured floors.

Exemptions (3 entry/entries excluded from the accounting above)
Module / Crate Reason Issue
crate: ironclaw_embeddings v1-only: consumed only by root ironclaw (src/app.rs, src/tools/builtin/memory.rs, src/workspace/mod.rs, src/config/{mod,embeddings}.rs); no crates/* dependents. Covered by "Tests (Legacy)". #5657
crate: ironclaw_gateway v1-only: consumed only by root ironclaw (src/channels/web/platform/static_files.rs, src/channels/web/handlers/frontend.rs); no crates/* dependents. Covered by "Tests (Legacy)". #5657
crate: ironclaw_tui v1-only: consumed only by root ironclaw (src/main.rs, src/channels/tui.rs); no crates/* dependents. Crate's own doc comment confirms it bridges INTO v1, not Reborn. Covered by "Tests (Legacy)". #5657

@railway-app

railway-app Bot commented Jul 8, 2026 •

Copy link
Copy Markdown

🚅 Deployed to the ironclaw-pr-5822 environment in ironclaw-ci-preview

Service Status Web Updated (UTC)
ironclaw ✅ Success (View Logs) Web Jul 10, 2026 at 5:06 am

…t-errors

# Conflicts:
#	crates/ironclaw_webui_v2/frontend/src/pages/chat/lib/failureMessages.test.mts
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5822 July 8, 2026 11:55 Destroyed
@italic-jinxin italic-jinxin self-assigned this Jul 8, 2026
…t-errors

# Conflicts:
#	crates/ironclaw_webui_v2/frontend/src/pages/chat/lib/useChat-send.test.mts
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5822 July 8, 2026 13:00 Destroyed
@github-actions github-actions Bot added size: XL 500+ changed lines and removed size: L 200-499 changed lines labels Jul 8, 2026
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5822 July 8, 2026 13:07 Destroyed
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5822 July 8, 2026 13:53 Destroyed
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5822 July 8, 2026 14:03 Destroyed
ironloopai[bot]

This comment was marked as resolved.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5822 July 9, 2026 12:46 Destroyed
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5822 July 9, 2026 12:49 Destroyed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/ironclaw_webui_v2/frontend/src/pages/chat/hooks/useChat.ts (1)

1004-1042: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Retry-failure path drops the inline error bubble it just claimed to restore.

removeFailed strips both message.id and failedRequestErrorId. On a successful retry that's correct — send will render its own bubbles if it fails again. But on the early-exit paths (response === null from admission block, or send throwing before its own try/catch, e.g. approvalGatePendingSendError()), restoreFailedIfNoReplacement pushes back only message:

return hasReplacement || prev.some((item) => item.id === message.id)
  ? prev
  : [...prev, message];

The comment at the catch site says this should "restore the original retryable error bubble," but no bubble is ever re-added — only the failed user message. The user is left with a retryable bubble and no explanation, which is exactly the floating/disappearing-error UX this PR sets out to fix. useChat-send.test.ts's new retry test only exercises the success path, so this gap is untested.

🐛 Proposed fix: restore the removed error bubble alongside the message
       const restoreFailedIfNoReplacement = (prev) => {
         const hasReplacement = prev.some(
           (item) =>
             item.id !== message.id &&
             item.role === CHAT_MESSAGE_ROLES.USER &&
             item.status === "error" &&
             item.retryContent === content,
         );
-        return hasReplacement || prev.some((item) => item.id === message.id)
-          ? prev
-          : [...prev, message];
+        if (hasReplacement || prev.some((item) => item.id === message.id)) {
+          return prev;
+        }
+        const restored = [...prev, message];
+        return message.error
+          ? [...restored, requestFailureMessageForContent(message.id, message.error)]
+          : restored;
       };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ironclaw_webui_v2/frontend/src/pages/chat/hooks/useChat.ts` around
lines 1004 - 1042, The retry rollback in useChat’s retry flow restores only the
original message but not the inline failed-request bubble that removeFailed
deletes. Update restoreFailedIfNoReplacement in useChat to re-add both message
and failedRequestErrorId on early-exit paths (response === null or caught send
failures) unless a replacement already exists, so the original retryable error
bubble is truly restored. Verify the retry handling around send, removeFailed,
and restoreFailedIfNoReplacement keeps the inline error visible for
admission-failure cases.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/ironclaw_webui_v2/src/static_assets/assets.rs`:
- Around line 282-283: The assertions in the asset test are checking outdated
string literals, so they no longer match the current `message-bubble.tsx`
source. Update the `message_bubble` `contains` checks to look for the
`CHAT_MESSAGE_ROLES.SYSTEM` and `CHAT_MESSAGE_ROLES.ERROR` references used in
the source text. Keep the fix localized to the test in `assets.rs`, using the
existing `source_text`/`message_bubble` assertions.

In `@tests/e2e/scenarios/test_reborn_webui_v2_legacy_pending_messages.py`:
- Line 278: The private helper handle_failed_send is missing an explicit return
type annotation, which triggers ANN202. Update the function signature in the
test helper to declare that it returns nothing by adding a None return
annotation, keeping the existing route/_payload/_fulfill_json parameters
unchanged.

---

Outside diff comments:
In `@crates/ironclaw_webui_v2/frontend/src/pages/chat/hooks/useChat.ts`:
- Around line 1004-1042: The retry rollback in useChat’s retry flow restores
only the original message but not the inline failed-request bubble that
removeFailed deletes. Update restoreFailedIfNoReplacement in useChat to re-add
both message and failedRequestErrorId on early-exit paths (response === null or
caught send failures) unless a replacement already exists, so the original
retryable error bubble is truly restored. Verify the retry handling around send,
removeFailed, and restoreFailedIfNoReplacement keeps the inline error visible
for admission-failure cases.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f63d39aa-2835-4ca1-ba41-a446f8004466

📥 Commits

Reviewing files that changed from the base of the PR and between b826c71 and a21e9d0.

📒 Files selected for processing (12)
  • crates/ironclaw_webui_v2/frontend/src/pages/chat/components/message-bubble.test.ts
  • crates/ironclaw_webui_v2/frontend/src/pages/chat/components/message-bubble.tsx
  • crates/ironclaw_webui_v2/frontend/src/pages/chat/hooks/useChat.ts
  • crates/ironclaw_webui_v2/frontend/src/pages/chat/lib/failureMessages.test.ts
  • crates/ironclaw_webui_v2/frontend/src/pages/chat/lib/failureMessages.ts
  • crates/ironclaw_webui_v2/frontend/src/pages/chat/lib/message-types.ts
  • crates/ironclaw_webui_v2/frontend/src/pages/chat/lib/useChat-send.test.ts
  • crates/ironclaw_webui_v2/frontend/src/pages/chat/lib/useChatEvents.test.ts
  • crates/ironclaw_webui_v2/frontend/src/pages/chat/lib/useChatEvents.ts
  • crates/ironclaw_webui_v2/src/static_assets/assets.rs
  • tests/e2e/reborn_coverage_tests.txt
  • tests/e2e/scenarios/test_reborn_webui_v2_legacy_pending_messages.py

Comment thread crates/ironclaw_webui_v2/src/static_assets/assets.rs Outdated
Comment thread tests/e2e/scenarios/test_reborn_webui_v2_legacy_pending_messages.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/ironclaw_webui_v2/frontend/src/pages/chat/hooks/useChat.ts (1)

1117-1122: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the dead helper — requestFailureMessageForError has no callers in crates/ironclaw_webui_v2/frontend/src and just wraps the same content path already used by appendRequestFailureMessage. Delete it, or wire the intended caller if one exists.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ironclaw_webui_v2/frontend/src/pages/chat/hooks/useChat.ts` around
lines 1117 - 1122, `requestFailureMessageForError` is a dead helper in
`useChat.ts` with no callers under the frontend src tree and it duplicates the
failure-message path already handled by `appendRequestFailureMessage`. Remove
the unused `requestFailureMessageForError` function, or if it is meant to be
used, update the relevant caller to reference it explicitly so there is a single
owned path for request failure message creation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@crates/ironclaw_webui_v2/frontend/src/pages/chat/hooks/useChat.ts`:
- Around line 1117-1122: `requestFailureMessageForError` is a dead helper in
`useChat.ts` with no callers under the frontend src tree and it duplicates the
failure-message path already handled by `appendRequestFailureMessage`. Remove
the unused `requestFailureMessageForError` function, or if it is meant to be
used, update the relevant caller to reference it explicitly so there is a single
owned path for request failure message creation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4e7751d7-8768-45ad-bdb4-d84f710719a1

📥 Commits

Reviewing files that changed from the base of the PR and between a21e9d0 and 1c99f52.

📒 Files selected for processing (4)
  • crates/ironclaw_webui_v2/frontend/src/pages/chat/hooks/useChat.ts
  • crates/ironclaw_webui_v2/frontend/src/pages/chat/lib/useChat-send.test.ts
  • crates/ironclaw_webui_v2/frontend/src/pages/chat/lib/useChatEvents.test.ts
  • crates/ironclaw_webui_v2/frontend/src/pages/chat/lib/useChatEvents.ts

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5822 July 9, 2026 13:29 Destroyed
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5822 July 9, 2026 13:51 Destroyed
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5822 July 9, 2026 13:56 Destroyed
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5822 July 9, 2026 14:25 Destroyed
@github-actions github-actions Bot added size: XL 500+ changed lines and removed size: L 200-499 changed lines labels Jul 9, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/ironclaw_webui_v2/frontend/src/pages/chat/hooks/useChat.ts (1)

1033-1050: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Retry restore path drops the inline error explanation.

removeFailed now strips the request-failure bubble (isRequestFailureForMessage) along with the user message, but restoreFailedIfNoReplacement only re-appends message — never the stripped bubble. When send() throws approvalGatePendingSendError() (thrown before send's own try/catch, so it never appends a replacement) or returns null from the busy/admission guards, retryMessage's catch/null branches call restoreFailedIfNoReplacement, so the user bubble reappears but its inline error explanation is gone for good — the exact regression this PR (#5708) is meant to fix. No existing test exercises this combination (an error bubble present + a blocked retry).

🐛 Proposed fix: capture and restore the stripped bubble
+      let removedRequestFailure;
       const removeFailed = (prev) =>
-        prev.filter(
-          (item) =>
-            item.id !== message.id &&
-            !isRequestFailureForMessage(item, message.id),
-        );
+        prev.filter((item) => {
+          if (item.id === message.id) return false;
+          if (isRequestFailureForMessage(item, message.id)) {
+            removedRequestFailure = removedRequestFailure || item;
+            return false;
+          }
+          return true;
+        });
       const restoreFailedIfNoReplacement = (prev) => {
         const hasReplacement = prev.some(
           (item) =>
             item.id !== message.id &&
             item.role === CHAT_MESSAGE_ROLES.USER &&
             item.status === "error" &&
             item.retryContent === content,
         );
-        return hasReplacement || prev.some((item) => item.id === message.id)
-          ? prev
-          : [...prev, message];
+        if (hasReplacement || prev.some((item) => item.id === message.id)) {
+          return prev;
+        }
+        return removedRequestFailure
+          ? [...prev, message, removedRequestFailure]
+          : [...prev, message];
       };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ironclaw_webui_v2/frontend/src/pages/chat/hooks/useChat.ts` around
lines 1033 - 1050, The retry restore path in useChat loses the inline
request-failure bubble because removeFailed filters out both the user message
and isRequestFailureForMessage, while restoreFailedIfNoReplacement only re-adds
message. Update retryMessage’s restore logic to preserve and restore the
stripped failure bubble when send() is blocked or throws
approvalGatePendingSendError(), using the existing helpers removeFailed,
restoreFailedIfNoReplacement, and isRequestFailureForMessage so the original
inline explanation returns with the user bubble.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@crates/ironclaw_webui_v2/frontend/src/pages/chat/hooks/useChat.ts`:
- Around line 1033-1050: The retry restore path in useChat loses the inline
request-failure bubble because removeFailed filters out both the user message
and isRequestFailureForMessage, while restoreFailedIfNoReplacement only re-adds
message. Update retryMessage’s restore logic to preserve and restore the
stripped failure bubble when send() is blocked or throws
approvalGatePendingSendError(), using the existing helpers removeFailed,
restoreFailedIfNoReplacement, and isRequestFailureForMessage so the original
inline explanation returns with the user bubble.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4a8d41dc-81f2-490b-95ab-d86eedeb3a1c

📥 Commits

Reviewing files that changed from the base of the PR and between 9177038 and 034d418.

📒 Files selected for processing (4)
  • crates/ironclaw_webui_v2/frontend/src/pages/chat/components/message-bubble.tsx
  • crates/ironclaw_webui_v2/frontend/src/pages/chat/hooks/useChat.ts
  • crates/ironclaw_webui_v2/frontend/src/pages/chat/lib/message-types.ts
  • crates/ironclaw_webui_v2/frontend/src/pages/chat/lib/useChat-send.test.ts

…t-errors

# Conflicts:
#	crates/ironclaw_webui_v2/frontend/src/pages/chat/components/message-bubble.tsx
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5822 July 10, 2026 04:57 Destroyed
@italic-jinxin italic-jinxin added the human-verified Manually tested and verified label Jul 10, 2026
@think-in-universe
think-in-universe added this pull request to the merge queue Jul 10, 2026
Merged via the queue into main with commit 464b201 Jul 10, 2026
62 checks passed
@think-in-universe
think-in-universe deleted the issue-5708-inline-chat-errors branch July 10, 2026 09:24

This branch was successfully deployed

No deployments
ironclaw-ci-preview / ironclaw-pr-5822 — 1a8df278 Deployed Jul 10, 2026 by railway-app[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs human-verified Manually tested and verified risk: low Changes to docs, tests, or low-risk modules size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error banners are displayed outside the chat message stream

2 participants