# Gpui Troubleshooting

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

- Skill: `zerx-lab/gpui-troubleshooting` (Agent Skill)
- Install (CLI): `npx skillmds@latest add zerx-lab/gpui-troubleshooting`
- Raw SKILL.md: https://api.skillmd.com/api/skills/zerx-lab/gpui-troubleshooting/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: zerx-lab (https://skillmd.com/u/zerx-lab)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/zerx-lab/gpui-troubleshooting

---


# 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

```rust
// ❌ 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

```rust
// ❌ 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

```rust
// ❌ 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

```rust
// ❌ 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

```rust
// ❌ 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

```rust
// ❌ 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

```rust
// ❌ 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

```rust
// ❌ 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>`

```rust
// ❌ 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

```rust
// ❌ 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

```rust
// ❌ 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

```rust
// 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:

```bash
# ❌ 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

```rust
// ❌ 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

```rust
// ❌ 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 Documentation](https://gpui.rs)
- [Zed GEMINI.md](https://github.com/zed-industries/zed/blob/main/GEMINI.md)

