gpui-troubleshooting

cnwzhu's avatarfrom cnwzhu

Common errors and solutions for GPUI development. Use when debugging build errors, runtime panics, borrow checker issues, or unexpected behavior.

0stars🔀0forks📁View on GitHub🕐Updated Jan 11, 2026

When & Why to Use This Skill

This Claude skill serves as a comprehensive troubleshooting guide for GPUI development, specifically designed to help developers resolve complex Rust-based issues. It provides actionable solutions for borrow checker errors, async task management, and UI synchronization, enabling faster debugging and more robust code implementation within the GPUI framework.

Use Cases

  • Resolving 'Multiple Borrow Errors' by identifying and correcting improper context (cx) usage within entity update closures.
  • Fixing UI synchronization issues where state changes are not reflected in the interface due to missing notification calls.
  • Debugging asynchronous execution problems, such as tasks being dropped prematurely or using non-GPUI timers in test environments.
  • Preventing runtime panics by implementing safe entity upgrading patterns and replacing unsafe unwraps with proper error handling.
  • Correcting trait implementation errors for UI components, such as Render and IntoElement signature mismatches.
namegpui-troubleshooting
descriptionCommon errors and solutions for GPUI development. Use when debugging build errors, runtime panics, borrow checker issues, or unexpected behavior.

GPUI Troubleshooting

This skill covers common errors and solutions when developing with GPUI.

Borrow Checker Errors

Multiple Borrow Error

Error: Cannot borrow cx as mutable more than once

Solution: Use the inner cx provided to closures, not the outer one

// ❌ WRONG
entity.update(cx, |view, inner_cx| {
    view.count += 1;
    cx.notify(); // Using outer cx - ERROR!
});

// ✅ CORRECT
entity.update(cx, |view, inner_cx| {
    view.count += 1;
    inner_cx.notify(); // Using inner cx
});

Moved Value Error

Error: Use of moved value in async block

Solution: Clone before moving into async block

// ❌ WRONG
cx.spawn(async move |this, cx| {
    self.data.do_something(); // ERROR: self moved
});

// ✅ CORRECT
let data = self.data.clone();
cx.spawn(async move |this, cx| {
    data.do_something();
});

Entity Errors

Update While Updating Panic

Error: already mutably borrowed: BorrowError

Cause: Trying to update an entity while it's already being updated

Solution: Avoid nested entity updates

// ❌ WRONG - will panic
entity.update(cx, |view, cx| {
    entity.update(cx, |view2, cx2| {
        // Nested update - PANIC!
    });
});

// ✅ CORRECT - restructure logic
entity.update(cx, |view, cx| {
    view.prepare_update();
});
// Update happens after first update completes
entity.update(cx, |view, cx| {
    view.apply_update();
});

WeakEntity Upgrade Failure

Error: Panic when calling methods on None

Solution: Always check upgrade() result

// ❌ WRONG
let entity = weak.upgrade().unwrap(); // May panic!

// ✅ CORRECT
if let Some(entity) = weak.upgrade() {
    entity.update(cx, |view, cx| {
        // ...
    });
} else {
    // Entity was dropped
}

// ✅ ALSO CORRECT (in async context)
this.update(&mut *cx, |view, cx| {
    // ...
})?; // Propagate error if entity gone

Async Errors

"Nothing Left to Run" in Tests

Error: run_until_parked() completes but test fails

Cause: Using smol::Timer instead of GPUI timers

Solution: Use GPUI executor timers

// ❌ WRONG
smol::Timer::after(Duration::from_secs(1)).await;

// ✅ CORRECT
cx.background_executor.timer(Duration::from_secs(1)).await;

Task Dropped Before Completion

Error: Async work doesn't complete

Cause: Task dropped without being awaited or detached

Solution: Either detach or store the task

// ❌ WRONG - task dropped immediately
cx.spawn(async move |this, cx| {
    // Work...
    Ok(())
});

// ✅ CORRECT - detach
cx.spawn(async move |this, cx| {
    // Work...
    Ok(())
}).detach();

// ✅ ALSO CORRECT - store
self.current_task = Some(cx.spawn(async move |this, cx| {
    // Work...
    Ok(())
}));

Async Context Borrow Error

Error: Cannot dereference cx in async context

Solution: Use &mut *cx to dereference

// ❌ WRONG
cx.spawn(async move |this, cx| {
    this.update(cx, |view, cx| { // ERROR!
        // ...
    });
});

// ✅ CORRECT
cx.spawn(async move |this, cx| {
    this.update(&mut *cx, |view, cx| {
        // ...
    })?;
    Ok(())
});

Trait Errors

IntoElement Not Implemented

Error: the trait IntoElement is not implemented for ...

Solution: Ensure type implements IntoElement or use .child() correctly

// ❌ WRONG
div().child(some_struct); // Error if SomeStruct doesn't impl IntoElement

// ✅ CORRECT - use entity
let entity = cx.new(|_| SomeStruct::new());
div().child(entity);

// ✅ ALSO CORRECT - implement RenderOnce
#[derive(IntoElement)]
struct SomeStruct;

impl RenderOnce for SomeStruct {
    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
        div().child("Content")
    }
}

Render Trait Signature Mismatch

Error: Method signature doesn't match trait

Solution: Ensure correct signature with Window and Context<Self>

// ❌ WRONG
impl Render for MyView {
    fn render(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
        // Missing window parameter
    }
}

// ✅ CORRECT
impl Render for MyView {
    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
        div().child("Content")
    }
}

UI Not Updating

Forgetting cx.notify()

Symptom: State changes but UI doesn't update

Solution: Call cx.notify() after state changes

// ❌ WRONG
fn increment(&mut self, cx: &mut Context<Self>) {
    self.count += 1;
    // Missing cx.notify() - UI won't update!
}

// ✅ CORRECT
fn increment(&mut self, cx: &mut Context<Self>) {
    self.count += 1;
    cx.notify(); // Trigger re-render
}

Subscription Dropped

Symptom: Events stop being received

Solution: Store Subscription in a field

// ❌ WRONG
impl Parent {
    fn new(cx: &mut Context<Self>) -> Self {
        let child = cx.new(|_| Child::new());
        
        cx.subscribe(&child, |this, _child, event, cx| {
            // Handle event
        }); // Subscription dropped here!
        
        Self { child }
    }
}

// ✅ CORRECT
struct Parent {
    child: Entity<Child>,
    _subscription: Subscription, // Store to keep alive
}

impl Parent {
    fn new(cx: &mut Context<Self>) -> Self {
        let child = cx.new(|_| Child::new());
        
        let subscription = cx.subscribe(&child, |this, _child, event, cx| {
            // Handle event
        });
        
        Self {
            child,
            _subscription: subscription,
        }
    }
}

Build Errors

Missing Imports

Error: Cannot find type/trait in this scope

Solution: Import from gpui::* or specific module

// Add at top of file
use gpui::*;

// Or specific imports
use gpui::{
    div, rgb, px,
    App, Context, Entity, Window,
    Render, IntoElement, RenderOnce,
};

Clippy Warnings

Use project's clippy script:

# ❌ WRONG
cargo clippy

# ✅ CORRECT (for Zed-based projects)
./script/clippy

Runtime Panics

Index Out of Bounds

Error: Panic from vector indexing

Solution: Use safe access methods

// ❌ WRONG
let item = self.items[index]; // May panic!

// ✅ CORRECT
if let Some(item) = self.items.get(index) {
    // Use item
}

// ✅ ALSO CORRECT
if index < self.items.len() {
    let item = &self.items[index];
}

Unwrap on None/Err

Error: called unwrap() on a None value

Solution: Never use unwrap() - use ? or proper error handling

// ❌ WRONG
let value = option.unwrap(); // May panic!
let result = fallible_op().unwrap(); // May panic!

// ✅ CORRECT - propagate error
let value = option.ok_or_else(|| anyhow::anyhow!("Missing value"))?;
let result = fallible_op()?;

// ✅ ALSO CORRECT - handle explicitly
match option {
    Some(value) => {
        // Use value
    }
    None => {
        // Handle missing case
    }
}

Common Mistakes Summary

Issue Cause Solution
Multiple borrow Using outer cx in closure Use inner cx
Update panic Nested entity updates Avoid nesting, restructure
UI not updating Missing cx.notify() Call after state changes
Task not running Task dropped Use .detach() or store task
Test timeout Using smol::Timer Use GPUI timer()
Events not received Subscription dropped Store in field
Weak entity panic Not checking upgrade() Use if let Some(...) or ?
Unwrap panic Calling .unwrap() Use ? or proper error handling

Debugging Tips

  1. Check the inner cx: In update closures, always use the inner cx
  2. Verify notify calls: Add cx.notify() after state changes
  3. Store subscriptions: Keep Subscription values in struct fields
  4. Use GPUI timers in tests: Replace smol::Timer with cx.background_executor.timer()
  5. Avoid unwrap: Use ? for error propagation or explicit error handling
  6. Check task lifecycle: Ensure tasks are detached or stored, not dropped
  7. Watch for nested updates: Avoid updating entities within update closures

References

gpui-troubleshooting – AI Agent Skills | Claude Skills