Quick answer: Wrap editor-only paths with Engine.IsEditorHint(). Defer static initialization to _Ready. Rebuild the C# project after changes and reopen the scene.

A tool script that generates UI elements at edit time works once. Reload the project; the editor crashes on scene load. The stack trace points to a null reference inside your tool class, before _Ready ever fires.

Tool Scripts Run in the Editor

The [Tool] attribute makes a class active during editor scene operations — opening, saving, reloading. The class is instantiated; its constructor runs; _Ready may fire if the script is in an open scene; _Process runs every editor frame.

None of this happens in pure runtime classes. Tool scripts have to handle “Am I in the editor?” and “Is the engine fully ready?”

Fix 1: Guard with IsEditorHint

[Tool]
public partial class ProcGenLevel : Node
{
    public override void _Ready()
    {
        if (Engine.IsEditorHint())
        {
            // Editor-only setup
            GenerateLevelLayout();
        }
        else
        {
            // Runtime initialization
            SpawnEnemies();
        }
    }
}

IsEditorHint returns true in the editor, false at runtime. Branch based on which environment you’re in.

Fix 2: Defer Static Initialization

// Wrong: runs as soon as the type loads, possibly before engine ready
private static readonly Texture2D _icon = ResourceLoader.Load<Texture2D>("res://icon.png");

// Right: lazy property
private static Texture2D _icon;
private static Texture2D Icon => _icon ??= ResourceLoader.Load<Texture2D>("res://icon.png");

Lazy initialization happens at first access, not at type load. By then, the engine is ready.

Fix 3: Rebuild After Code Changes

Godot caches compiled C# assemblies. Editing a tool script and reopening the scene without rebuilding can leave stale references that crash. Always:

  1. Save the C# file.
  2. Project → Tools → C# → Build (or use the build button in the toolbar).
  3. Wait for build to complete.
  4. Then reload/reopen the affected scene.

Fix 4: Avoid Heavy _Process in Tool

Don’t do continuous expensive work in editor _Process — it ticks every editor frame, even when you’re not focused on the relevant scene. Gate with IsEditorHint and use Engine.GetProcessFrames() % N == 0 to throttle.

Verifying

Reload the project. Open the scene with the tool script. No crash. Edit the script; rebuild; reload — still no crash. The editor behaves normally with the tool functionality active.

“Tool scripts run twice: editor and runtime. Branch on IsEditorHint and the editor stays stable.”

When in doubt, comment out the [Tool] attribute to confirm the crash is tool-related — isolates the diagnosis quickly.