Quick answer: Godot calls _ready() bottom-up. The deepest child nodes get their _ready() called first, then their parents, and finally the root of the scene. This means a parent's _ready() runs after all children are ready.
Here is how to fix Godot signal firing before child nodes ready. A parent node emits a signal in its _ready() function, but the child nodes that should respond to it have not finished initializing yet. Or the reverse — a child emits a signal during _ready(), but the parent has not connected to it yet. The result is missed signals, null references, and initialization bugs that only appear intermittently. Understanding Godot’s _ready() call order is the key to fixing this.
The Symptom
You have a scene with a parent node and several children. One node emits a signal during _ready() to notify others about its initial state. But the receiving node’s callback never fires, or it fires but crashes because it tries to access a sibling node that has not initialized yet.
# Parent script: game_manager.gd
extends Node
signal game_initialized(config: Dictionary)
func _ready():
var config = load_config()
game_initialized.emit(config) # Emits too early!
# Child script: hud.gd
extends Control
func _ready():
# This runs BEFORE the parent's _ready()
var manager = get_parent()
manager.game_initialized.connect(_on_game_initialized)
func _on_game_initialized(config: Dictionary):
print("Config received: ", config) # Never prints!
The child connects to the parent’s signal in its own _ready(). The child’s _ready() runs first (children before parents), so the connection is established before the parent is ready. But when the parent’s _ready() fires and emits the signal, the child’s callback should work — unless the parent emits during its _ready() and the child is still processing other initialization steps, or the connection was made on the wrong node.
The more common problem is the reverse: a child emits during its _ready(), but the parent has not connected yet because the parent’s _ready() has not run.
What Causes This
The _ready() call order in Godot is bottom-up. The deepest children in the scene tree get their _ready() called first, then their parents, and finally the scene root. This means:
- A child’s
_ready()always runs before its parent’s_ready(). - Sibling nodes get their
_ready()called in tree order (top to bottom in the editor), but a sibling lower in the tree may not be ready when a higher sibling’s_ready()runs. - If a child emits a signal during
_ready(), the parent’s_ready()has not run yet, so any connection the parent was going to set up does not exist.
@onready variables compound the problem. Variables declared with @onready are assigned during the node’s _ready() phase. If a signal callback references an @onready variable on a node whose _ready() has not run yet, the variable is still null.
The Fix
Step 1: Use await owner.ready to wait for the full scene to initialize. The owner property points to the root node of the scene file. By awaiting its ready signal, you ensure that all nodes in the scene have completed their _ready() before your code continues:
# Child script that needs siblings/parent to be ready
extends Control
func _ready():
# Wait for the entire scene to be ready
await owner.ready
# Now it is safe to connect to parent signals
var manager = get_parent()
manager.game_initialized.connect(_on_game_initialized)
# And safe to access sibling nodes
var hud = get_node("../HUD")
hud.update_display()
Step 2: Defer signal emissions that depend on the full tree being ready. If a node needs to announce its initial state to other nodes, use call_deferred() to push the emission past the initialization phase:
# Parent that needs all children ready before emitting
extends Node
signal game_initialized(config: Dictionary)
func _ready():
var config = load_config()
# Defer the emission to after the full tree is ready
call_deferred("_emit_initialized", config)
func _emit_initialized(config: Dictionary):
game_initialized.emit(config)
Step 3: Use a two-phase initialization pattern for complex scenes. For scenes where multiple nodes need to exchange state during setup, separate the connection phase from the initialization phase:
# Phase 1: Connect signals in _ready() (runs bottom-up)
# Phase 2: Initialize state in a deferred call (runs after all _ready)
extends Node
signal state_ready(initial_state: Dictionary)
func _ready():
# Phase 1: Just set up connections
for child in get_children():
if child.has_method("_on_state_ready"):
state_ready.connect(child._on_state_ready)
# Phase 2: Emit after all _ready() calls complete
call_deferred("_initialize_state")
func _initialize_state():
var state = {"level": 1, "score": 0}
state_ready.emit(state)
Why This Works
The await owner.ready pattern works because the owner node is the last node in the scene to receive its _ready() call (since it is the root of the scene and _ready() is called bottom-up). When you await this signal, your coroutine pauses until after every node in the scene has completed initialization. By the time execution resumes, all @onready variables are set, all sibling connections are established, and all child nodes are fully initialized.
The call_deferred() approach works because deferred calls are processed during the idle phase of the main loop, which happens after the scene tree has finished processing all pending _ready() notifications. By deferring signal emission, you guarantee that the emission happens in a frame where all nodes are fully ready.
The two-phase initialization pattern combines both approaches. Connections are set up in _ready() (which is safe because children connect before parents), and actual state distribution is deferred to after the initialization phase. This gives you a clean separation between wiring and execution.
“If you emit a signal in _ready(), you are broadcasting to an audience that might not be seated yet. Defer the broadcast.”
Related Issues
If the signal connects fine but the callback never executes even after initialization, the node might have left the tree. See our guide on fixing signals that are connected but whose callbacks never fire.
If you are getting “Invalid get index on base Nil” errors in your callbacks because @onready variables are null, this is a closely related timing issue. See fixing Invalid get index on base Nil errors.
For signals that fire the correct number of times during init but then fire again unexpectedly, you may have duplicate connections. Check fixing duplicate signal connections.
Children first, parents last. Defer what depends on the whole tree.