Summary
Claude runs perfectly (backend works), but the frontend never receives any Tauri events — resulting in infinite loading spinner, no response shown, cancel button broken (claudeSessionId stays null).
Root Cause
ClaudeCodeSession.tsx loads the Tauri event listener via require() inside a try/catch:
// src/components/ClaudeCodeSession.tsx
let tauriListen: any;
try {
if (typeof window !== 'undefined' && window.__TAURI__) {
tauriListen = require("@tauri-apps/api/event").listen; // ← BUG
}
} catch (e) {
console.log('[ClaudeCodeSession] Tauri APIs not available, using web mode');
}
const listen = tauriListen || domEventFallback;
Vite bundles as ESM — require is not defined at runtime → ReferenceError is thrown → silently caught → tauriListen stays undefined → listen = DOM event fallback.
The DOM fallback listens via window.addEventListener(eventName, ...) and expects window.dispatchEvent(new CustomEvent(...)). But the Rust backend emits events through Tauri's IPC bridge, not the DOM — so the frontend receives zero events.
Symptoms
- Infinite loading animation after sending a message
- No response rendered (though Claude responds correctly — verified via
RUST_LOG=debug)
- Cancel button does nothing (requires
claudeSessionId which is set from the init event — never received)
- Debug overlay shows:
loading:true | sid:null | msgs:1 | listening:true
Affected
All macOS (and likely Linux) users building from source with Vite. The require() anti-pattern in ESM is the culprit.
Fix
Replace the require() pattern with a proper static ESM import:
- let tauriListen: any;
- try {
- if (typeof window !== 'undefined' && window.__TAURI__) {
- tauriListen = require("@tauri-apps/api/event").listen;
- }
- } catch (e) {
- console.log('[ClaudeCodeSession] Tauri APIs not available, using web mode');
- }
-
- const listen = tauriListen || ((eventName, callback) => {
- window.addEventListener(eventName, ...);
- return Promise.resolve(...);
- });
+ import { listen } from "@tauri-apps/api/event";
One line. Fixes all event delivery.
Verification
After the fix, the debug overlay shows correct progression:
- Before send:
loading:false | sid:null | msgs:0 | listening:false
- After response:
loading:false | sid:d49637f9 | msgs:3 | listening:false
Claude responses render correctly and loading state clears.
Notes
The same require() pattern exists in useClaudeMessages.ts (for claude-stream events) — worth fixing there too for consistency, though that code path may not be active by default.
Summary
Claude runs perfectly (backend works), but the frontend never receives any Tauri events — resulting in infinite loading spinner, no response shown, cancel button broken (
claudeSessionIdstays null).Root Cause
ClaudeCodeSession.tsxloads the Tauri event listener viarequire()inside atry/catch:Vite bundles as ESM —
requireis not defined at runtime →ReferenceErroris thrown → silently caught →tauriListenstaysundefined→listen= DOM event fallback.The DOM fallback listens via
window.addEventListener(eventName, ...)and expectswindow.dispatchEvent(new CustomEvent(...)). But the Rust backend emits events through Tauri's IPC bridge, not the DOM — so the frontend receives zero events.Symptoms
RUST_LOG=debug)claudeSessionIdwhich is set from theinitevent — never received)loading:true | sid:null | msgs:1 | listening:trueAffected
All macOS (and likely Linux) users building from source with Vite. The
require()anti-pattern in ESM is the culprit.Fix
Replace the
require()pattern with a proper static ESM import:One line. Fixes all event delivery.
Verification
After the fix, the debug overlay shows correct progression:
loading:false | sid:null | msgs:0 | listening:falseloading:false | sid:d49637f9 | msgs:3 | listening:falseClaude responses render correctly and loading state clears.
Notes
The same
require()pattern exists inuseClaudeMessages.ts(forclaude-streamevents) — worth fixing there too for consistency, though that code path may not be active by default.