Review and validate changes
A generated scene is a diff like any other, and the same review question applies: does this file say what someone meant it to say. These commands answer it without opening the editor.
Validate
godot-cli scene validate scenes/main.tscn --project-root . --json
godot-cli scene validate-batch scenes/*.tscn --project-root . --json
Validation exits 1 when it finds an error, and returns every issue with a line number, a severity, and a machine-readable kind:
| Kind | Severity | What it means |
|---|---|---|
duplicate_scene_id |
error | Two ext or sub resources in the file share an id |
node_parent_order |
error | A node's parent refers to a node declared later in the file, which Godot reads as a parent path that vanished |
resource_section_order |
error | An ext_resource appears after a sub_resource |
stale_uid_for_path |
error | A uid:// reference disagrees with the project's UID cache |
nonstandard_scene_id |
warning | An id does not match the shape the editor writes, such as 1_a instead of 1_abc12 |
invalid_node_unique_id |
warning | A node's unique_id is not a value Godot would generate |
property_type_mismatch |
error | A property's value is the wrong Variant type for its class, e.g. visible = Vector2(1, 2) |
unknown_signal |
error | A connection names a signal the emitting class does not have, and its script does not declare |
unknown_property |
warning | A property the class does not have, on a node with no script and no instance: Godot keeps the line and ignores it |
connection_node_missing |
error | A connection names a node that is not in the scene |
control_under_node2d |
warning | A Control under a Node2D has no parent rect, so anchors give it no size and it may draw nothing |
dangling_ext_resource |
error | A property references an ExtResource id nothing declares |
The checks that read the file the way Godot would
property_type_mismatch, unknown_signal and unknown_property come from a
table generated out of Godot's own class reference — 520 classes, 3933
properties, 377 signals — so they catch the mistakes that otherwise cost a run
to find:
[err] property_type_mismatch: Control.visible is bool, and this value is vector2:
Godot coerces it and the file still loads, so only the running frame shows the damage
[err] unknown_signal: Button does not emit a signal named pressd, and its script does not
declare one either, so this connection never fires
[warn] unknown_property: HBoxContainer has no property margin_left, so Godot keeps the line
and ignores it: the setting does nothing and nothing reports it
unknown_property is a warning rather than an error because the table cannot
see everything a property might come from. A node with a script attached, or an
instanced node, is skipped entirely — their @export vars are not in any class
reference. So are namespaced names like theme_override_constants/margin_left
and metadata/level, and the handful Godot registers as internal, layout_mode
and anchors_preset among them, which the editor writes into every scene it
touches and the class reference never mentions.
Both are deliberately quiet about anything the table cannot speak for: a class
it does not carry, a theme_override_* entry, a metadata/* key, a script's
exported variables, and a connection whose endpoint lives inside an instance.
If a run answers uid_cache_unreadable, the damaged file is Godot's
.godot/uid_cache.bin and not the scene you passed — delete it and run the
project once to rebuild it. scene validate itself does not fail on that any
more; it skips the stale_uid_for_path check and says so in messages.
So read a clean scene validate for what it is. It says nothing here was
found wrong; it does not say every property is real, every signal exists, or
every value is right. unknown_property in particular skips more than it
checks — a misspelled theme_override_constants/marign_left passes, and so
does any property on a node that has a script. A partial check that says
nothing reads exactly like a complete one that found nothing, which is why the
step after validating is running the game
rather than calling it done.
A correct scene is never reported. With --project-root the emitting node's
script is read too, so a signal the script declares itself passes.
A real one reads like this:
{
"severity": "err",
"kind": "node_parent_order",
"message": "node 'Player' parent 'Playfield' is declared later in the file (Godot instantiate: parent path vanished)",
"line": 5
}
A validate that found errors exits 1 and answers ok: false with a
checks_failed failure, keeping the issues in data — so ok, the exit code,
and the error flag an MCP client sets all agree.
stale_uid_for_path needs --project-root, since it is a question about the project, not the file on its own. Resources get the same treatment with resource validate and resource validate-batch.
Validation does not open the files a scene points at. For that, scene refs resolves every ext_resource to a filesystem path and reports whether it is there:
godot-cli scene refs scenes/main.tscn --project-root . --json
{ "id": "1_a", "type": "Script", "path": "res://missing.gd",
"filesystem_path": "./missing.gd", "exists": false }
Diff
godot-cli scene diff before.tscn after.tscn --json
godot-cli scene diff before.tscn after.tscn --properties --json
Without --properties you get added, removed, and retyped nodes, which is the level to review a structural change at. With it you also get changed property values, which is what you want when the tree is the same and something moved.
The diff is between two files, so keep a copy before an edit, or use a snapshot:
godot-cli scene apply scenes/main.tscn --patch change.json --project-root . \
--snapshot scenes/main.before.tscn --json
godot-cli scene diff scenes/main.before.tscn scenes/main.tscn --properties --json
Read the file without reading the text
godot-cli scene node list scenes/main.tscn --json
godot-cli scene node get scenes/main.tscn /root/Main/Player --json
godot-cli scene inspect scenes/main.tscn --json
inspect returns each section with its properties parsed: a name, the Variant kind, the raw text, and a typed value where one is available. A property that failed to parse comes back with parse_error set rather than failing the whole command, which is how you find a value that is subtly malformed.
Undo a change
godot-cli scene restore scenes/main.tscn --snapshot scenes/main.before.tscn
Or apply the undo patch that --write-undo-patch produced during the edit. Both take --dry-run.
What to check in review
Validation passing means the file is well formed, not that the change is right. The things worth a human eye:
Nodes are in the tree, not created in _ready(). scene node list shows the structure that will exist when the scene opens; if a HUD is missing from it, the layout is being built in code.
Reused components are instanced, not copied. An instanced component shows as a node with instance=ExtResource(...) and no children in the parent scene. A copied one shows as a subtree that duplicates the component.
Presentation is on the nodes, not only in scripts. scene inspect lists the theme overrides and layout properties, so a Control with no styling and a script full of add_theme_*_override calls is easy to spot.
In CI
godot-cli scene validate-batch scenes/*.tscn --project-root . --json
godot-cli catalog validate --project-root . --json
Both exit 1 on failure, so they work as build steps. If you generate the catalog digest, regenerate it and check the tree is clean:
godot-cli catalog export --project-root . --output AGENTS.md
git diff --exit-code AGENTS.md