Quick answer: Almost always another Control is on top of the button and swallowing the click — a full-screen Panel, ColorRect or container with mouse_filter set to Stop. Set purely visual Controls to Ignore. Use the debugger's Misc tab, which shows the Control that received the last click.
Buttons have a tiny set of failure modes: something is covering them, they are disabled, they are not connected to the signal you think, or input never reaches the GUI at all. The debugger can tell you which one in seconds.
Find out what was actually clicked
Run the game, click the button, then open the Debugger panel's Misc tab. It shows *Clicked Control* and its type. If it names a Panel, TextureRect or ColorRect instead of your Button, that node is covering it.
Cause 1: an overlay with mouse_filter = Stop
Every Control has a Mouse > Filter:
- Stop — consumes mouse events (the default for most Controls, including Panel and ColorRect).
- Pass — handles events and lets them continue to Controls underneath.
- Ignore — invisible to the mouse.
A full-screen fade ColorRect, a transparent Control used as a layout root, or a dialog's dimmer placed later in the tree sits above the button and stops the click. Set decorative Controls and layout roots to Ignore:
$FadeOverlay.mouse_filter = Control.MOUSE_FILTER_IGNORE
Remember that a later CanvasLayer with a higher layer is drawn above — and receives input before — everything on lower layers.
Cause 2: the wrong signal or no connection
pressed fires on release by default (Action Mode: Button Release); button_down fires on press. If the mouse moves off the button before release, pressed does not fire. Confirm the connection exists in the Node dock, and in code use $Button.pressed.connect(_on_button_pressed).
Cause 3: disabled or toggle mode
A disabled button shows the disabled style and ignores input. With Toggle Mode on, use toggled(button_pressed) if you care about state; pressed still fires but the button stays down.
Cause 4: input consumed before the GUI
Godot delivers input to _input() first, then to Controls, then to _unhandled_input(). A script that calls get_viewport().set_input_as_handled() in _input() for mouse buttons stops the click from ever reaching the button. Move gameplay click handling into _unhandled_input(), which only receives what the UI did not use.
Cause 5: the button lives in a SubViewport
Controls inside a SubViewport only get input if the SubViewport is displayed by a SubViewportContainer (which forwards events) or you push events into it yourself with push_input(). A SubViewport rendered onto a TextureRect or a 3D mesh does not receive clicks automatically.
Cause 6: zero-size or clipped buttons
A button inside a container can collapse to size zero if its size flags and minimum size allow it, and a parent with Clip Contents cuts off clicks outside its rectangle. Select the button in the Remote scene tree while the game runs and check its size and global position.