Why an Action Did Not Emit
Hengo never emits half an action. When something is wrong it drops the action entirely and leaves a loud trace in both places you might look:
# hengo: action set_value (a12) unresolved: input "Target" must be bound to a variable or propertyThe same text is pushed as a warning to Godot’s Output panel. You also see it without leaving Hengo: the Actions counter in the top bar goes up, clicking it lists the reasons, and the card that caused each one is drawn red on the canvas.
Search the generated file for # hengo: after a compile, an empty phase method with a marker in it
is always this.
The reasons
Section titled “The reasons”| Marker text | What happened | Fix |
|---|---|---|
macro not found | The saved action points at an id no file declares any more. | Restore the file, or keep get_id() stable when refactoring. |
could not instance macro | The file loaded but is not a HenScriptMacroBase. | Check @tool and the extends. A parse error in the action file also lands here. |
| your own text | get_validation_error() returned a reason. | Whatever your action decided it needs. |
<name> can only be used inside a loop | An action with get_needs_loop() sits at the top level. | Move it inside a Repeat or For Each. |
<name> can only be used inside a function | An action with get_needs_function(), such as Finish, sits in a state. | Move it into a function body. |
this finish belongs to another function | A Finish for one function ended up in a different one. | Use the Finish of the function you are in. |
a function cannot change the state of its own script | A branch inside a function targets a state of the same script. | Report a way out instead, and let the caller decide. |
has no <phase> body | The action is on a phase it never declared a get_flow_<phase>() for. | Change the action’s phase, or declare that body. The message uses the internal phase id, so update is the Every Frame cell. |
input "X" must be bound to a variable or property | An lvalue or bind_only slot has no usable source. An expression or an inline action never satisfies one. | Bind the slot to a variable, a property or a node. |
input "X" binds a variable that no longer exists | The bound variable was deleted or renamed away. | Rebind the slot. Undoing the delete restores the binding, the link is by id. |
expression word "w" binds a variable that no longer exists | Same, for a word inside an expression slot. | Rebind the word. |
input "X" reads a step that no longer exists | A wire pointed at a step that was deleted. | Re-drag the wire, or give the slot another source. |
input "X" inline action is empty | An inline slot lost its nested action. | Re-attach the producer. |
input "X" inline action could not be resolved | The nested action’s macro is missing. | Same causes as macro not found, one level down. |
input "X" inline action is not a pure value producer | The nested action branches, loops or keeps state. | Store its result in a variable and bind the slot to that variable. |
no output stored | A producer’s outputs are all unbound and nothing wires them, so the body has nothing left to run. | Store at least one output, wire it into a step, or remove the action. |
a branching action cannot run on exit | A transition on the End cell would re-enter the state forever. Steps alone are fine there. | Move the transition to Start, Every Frame or Physics. |
no branch target set | An action with required branches points nowhere and runs nothing. | Set a target, or hang steps on a branch. A branch declared optional never triggers this. |
branch "X": missing target instance connection | A cross-script branch has no instance to drive. | Give the branch an instance, a bound variable or a node path. |
branch "X" points at a sub-state of another state | A sub-state is only reachable from its own parent chain. | Target a top-level state, a sibling, or a sub-state of the running chain. |
Common traps when writing one
Section titled “Common traps when writing one”The action does not show up in the search.
Hengo scans res://hengo/macros/ when a collection loads, so reopen it. Also check @tool, and
check get_target_classes(): an action gated to Node2D is invisible on a Control script.
Two copies of the action collide.
Any name your body emits must carry {{VCNODE_ID}}, including local variables, class members and
callbacks. Without it the second copy redeclares the first.
A body using delta fails to compile.
delta only exists in update and physics. Declare only those flow inputs when the body needs it.
{{input}} came out literally in the code.
get_script_base(), get_script_scope() and get_function_overrides() do not run the input
substitution. Read the value with value_of(&'id', fallback) and paste it into the string.
A string input emitted quotes you did not want.
That is the default: values are emitted as literals. Set raw = true on the slot to emit the text
verbatim, which is what pickers built with options need.
A counter keeps its value when the state is re-entered.
State objects are built once. Zero it in get_flow_reset(), which runs at the top of enter().
A signal stays connected after leaving the state.
Disconnect in get_flow_teardown(), and resolve the emitter once in get_flow_reset() instead of
calling get_node() again on the way out.
Reading the output
Section titled “Reading the output”The generated .gd under res://hengo/scripts/ is the source of truth for what an action emitted.
When a body looks right but behaves wrong, read the emitted lines before touching the template.
Back to Write a Custom Action, or the method-by-method Action API.