From godot-dev
Debug Godot 4.x errors, crashes, and unexpected behavior. Corrective lens for Godot 3 vs 4 API confusion, async pitfalls, lifecycle timing, and physics gotchas.
How this skill is triggered — by the user, by Claude, or both
Slash command
/godot-dev:godot-debuggingsonnetThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
1. Read the FULL error message — "ON BASE: NULL INSTANCE" matters more than the property name
print_stack() for call chain, not just print()Models frequently suggest deprecated Godot 3 APIs. Always use the Godot 4 equivalents:
| Godot 3 (WRONG) | Godot 4 (CORRECT) | Notes |
|---|---|---|
deg2rad() | deg_to_rad() | All math utils renamed |
rand_range() | randf_range() | Also randi_range() for int |
connect("signal", obj, "method") | signal_name.connect(callable) | Callable-based signals |
move_and_slide(velocity, up) | move_and_slide() | velocity is now a property |
BUTTON_LEFT | MOUSE_BUTTON_LEFT | All input enums renamed |
yield(obj, "signal") | await obj.signal | yield removed entirely |
instance() | instantiate() | PackedScene method |
KinematicBody2D | CharacterBody2D | Node type renamed |
Sprite | Sprite2D | 2D suffix added |
export var | @export var | Annotation syntax |
onready var | @onready var | Annotation syntax |
set_cell(x, y, id) | set_cell(layer, coords, source, atlas, alt) | TileMap completely reworked |
await on signal that fires instantly → returns immediately (not next frame)await tween.finished when duration=0 → hangs foreverawait → silently becomes dangling task, no errorawait → crash (coroutine references freed objects)await does NOT pause the caller unless the caller also uses await:
death_sequence() # Returns immediately — does NOT wait
await death_sequence() # This DOES wait
_ready() is bottom-up (children first, then parent) — NOT top-down@onready vars resolved BEFORE _ready() body runsinstantiate() without add_child() → node exists but NOT in tree (no _ready, no _process)_init() runs before tree entry — no access to $Children or get_parent()queue_free() doesn't happen immediately — node is still accessible until end of frameis_action_just_pressed() in _physics_process() can miss inputs at low framerates_unhandled_input() for game input, _input() only for UIInput.get_vector() returns normalized vector — no need to .normalized() againmove_and_slide() modifies velocity (wall sliding) — read velocity AFTER call, not beforecall_deferred() required for collision shape changes during physics callbacksRayCast2D/3D must be enabled AND force_raycast_update() called for immediate resultsIf unsure about exact API signatures for Godot 4.4+, use Context7:
mcp__claude_ai_Context7__resolve-library-id → find "godot"mcp__claude_ai_Context7__query-docs → get current API detailsgdformat + gdlint on all .gd filespush_error() for unrecoverable, push_warning() for suspicious but safeassert() for debug-only invariants (stripped in release)Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub haingt-dev/agent --plugin godot-dev