Error messages

This page catalogs every message the editor surfaces as an Issue in the Validation panel, a blocking dialog, or the crash banner. Each entry explains what the message means and how to clear it. For cases where the runtime is working as designed and it still surprised you, see Common pitfalls.

Where messages come from

Most messages appear as rows in the Validation panel’s Errors or Warnings list; a few show up as blocking dialogs (Export blocked, Invalid project) or, rarely, as a crash banner across the top of the editor. The rest of this page groups them by feature.

1. Scenes

1.1 Project has no entry scene

The project’s Entry scene is unset in Project Settings, so when the runtime starts there is no scene to activate. Nothing loads, and every other message about missing state cascades from that. The panel row names the project’s own record.

Open Project Settings and pick an entry scene from the dropdown. Any scene in the project qualifies; most projects nominate a top-level menu or opening dialogue scene. See Scenes and visit state.

1.2 Scene’s parent doesn’t exist

The scene declares a Parent scene that isn’t in the project. The usual cause is that the parent was deleted (or renamed and never re-picked) and the child now points at a stale uuid. The scene still authors and simulates, but every reference to its parent breaks.

Open the scene inspector and either clear Parent or pick a scene that exists. If the child scene is orphaned by intent, clearing is right; if it was meant to nest, repointing preserves the completion path. See Scenes and visit state.

1.3 Entry scene’s Complete scene has nowhere to go

A Complete scene event fires from a node in the project’s entry scene, and nothing in the scene hands control forward. Completing the entry scene with no successor dead-ends the run, so the panel flags it at authoring time. Only entry-scene Complete scene events are checked; nested scenes complete back into their parent, which is fine.

Any one of five follow-ups clears the warning: fire Complete project alongside the Complete scene to end the story, add an Enter scene (to a non-entry scene) or Move to event that hands off, set the entry scene’s Outro to a node that fires Complete project, wire On exhausted to a node that fires Enter scene (to a non-entry scene) or Complete project, or set Starting location in Project Settings so the world layer takes over on completion. See Scenes and visit state.

1.4 Scene writes to a variable it imports as read-only

A Set variable or Set variable text event inside the scene writes to a variable the scene imported in Read mode. Read imports promise the runtime that the scene only observes the variable, so the write breaks the contract and the panel rejects it at authoring time.

Open the scene inspector and change the import’s mode from Read to Read-write if the write was intended, or open the flagged event and either point it at a different variable or delete it. See Scenes and visit state.

1.5 Scene lifecycle field points at a missing or foreign node

The scene’s Intro, Update, Outro, On leave, or On exhausted field references a node that no longer exists, a node that belongs to a different scene, or a node whose Visible when isn’t false. Lifecycle nodes are meant to be internal to their scene and unreachable as choices; a dangling or shared node breaks the invariant.

Open the scene inspector and repoint the field at a node inside the scene, or clear it. On the flagged node itself, set Visible when to false so nothing else picks it as a choice. See Scenes and visit state.

1.6 Encounter trigger references a missing character or item

The encounter’s Trigger is set to Character or Item, but the picked entity is no longer in the project. Usually a rename or delete on the character or item side that didn’t propagate, or an import from a project that had the entity.

Open the encounter and pick a live character or item, or change the trigger to one that doesn’t reference the deleted entity. See Encounters.

1.7 Scene teardown node runs an Enter scene with Return after jump on

The scene’s Outro or On leave node contains a Start scene event whose Return after jump is on, directly or via a Jump chain. Only the return-here form breaks: the runtime would push the target scene, finish closing the source scene, then try to return into a scene that no longer exists. Hard transitions (Return after jump off) are allowed, and are the normal way to hand off from teardown.

Open the flagged event and turn Return after jump off if the transition is meant to be final, or move the Start scene onto a node reached after teardown finishes if the flow really does need to return. See Scenes and visit state.

2. Nodes

2.1 Two nodes have the same id

Two nodes in the project share the same Id. Every node id must be unique so expressions and events can address the node unambiguously; when two nodes claim the same id, references to it resolve to whichever the runtime saw first. The panel row points at both siblings.

Open one of the flagged nodes and rename its Id to something unique. Jump to the sibling from the row and confirm the reference sites now resolve where you expect. See Nodes and visibility.

2.2 Node has no title and nothing reaches it

The node has no Title and no other node or scene points at it: no Jump event targets it, and no scene’s Intro, Update, or Outro slot references it. Titleless nodes never surface in the simulator’s choice panel, so a titleless node with no inbound reach is unreachable in every mode.

Give the node a Title if you want it to surface as a choice, wire an inbound Jump or set it as a scene’s Intro, Update, Outro, On leave, or On exhausted node if it’s meant to run as logic, or delete it if it’s a leftover from an earlier draft. Any one of those clears the warning. See Nodes and visibility.

2.3 Event after a Jump can’t fire

The node’s event list has an event after a Jump with Return after jump off. The runtime never returns to the source node, so events sitting after the jump never execute. Common when a writer adds a follow-up action expecting the flow to resume.

Move the event before the Jump, delete it, or turn on Return after jump if the flow was meant to resume at the source. See Nodes and visibility.

3. Expressions

3.1 Bare identifier isn’t declared

An expression references a name that isn’t in the project’s variables, and the name isn’t a known function or constant either. Common shapes: a variable was renamed elsewhere without updating the reference, the writer meant a function call but wrote it as a bare name, or the identifier was left as a placeholder from an earlier draft.

Open Variables and either add the identifier, correct the typo, or wrap the reference in the function you meant. The expression field autocomplete shows every live identifier as you type. See Expressions.

3.2 Played or Selected references a location

Played(x) or Selected(x) was called with an identifier that names a location, not a node or tag. Per-scene visit-state applies to nodes; locations have no scene to scope against, so the check has nothing to evaluate.

Use PlayedEver or SelectedEver if you want the session-global check that survives across scene re-activations, or point the call at a node instead. See Expressions.

3.3 Played or Selected on a scene-less node

Played(x) or Selected(x) was called against a node that has no Scene assignment. Per-scene visit-state needs a scene to scope against; without one, the check has no meaning and evaluates as if the node were never played.

Assign the target node to a scene, or switch the call to PlayedEver or SelectedEver if session-global scope is what you actually want. See Expressions.

3.4 Played or Selected reaches across scenes

Played(x) or Selected(x) was called from a node whose scene isn’t the same as the target node’s scene. Per-scene visit-state doesn’t answer cross-scene questions: the target’s visit-state resets on every re-activation of its owning scene, so the answer flips depending on when you ask.

Use PlayedEver or SelectedEver instead if you want to know whether the node was ever played across the whole run. See Expressions.

3.5 Played or Selected asks about something that doesn’t exist

The identifier passed to Played, Selected, PlayedEver, or SelectedEver doesn’t name a node or tag in the project. Usually a typo, a rename that didn’t propagate, or a leftover reference to a deleted node.

Correct the identifier to a live node or tag, or remove the call if the target is gone for good. The expression field autocomplete lists every valid target. See Expressions.

4. Variables

4.1 Read-only variable is written to

A Set variable event targets a variable whose Read-only flag is on. Read-only variables can only be initialized at project start; runtime writes are blocked so downstream expressions see a stable value across the whole run.

Either turn off Read-only on the variable, or delete the write event. If the write was intended as one-time initialization, model it on the variable’s Default instead. See State.

4.2 Variable is declared but never used

This is a Warning; export still runs. The variable exists in the project’s variables list but no expression, event, or scene reads or writes it. Not a bug on its own, and often a leftover from an early draft that hasn’t been wired in yet.

Delete the variable if it’s stale, or leave the Warning as a note-to-self if it’s placeholder scaffolding you intend to wire up later. See State.

5. Dialogue and text

5.1 Dialogue text is empty

A Dialogue event has no text in Body. The runtime fires the caption anyway, but with nothing to say, downstream systems see a blank line. In practice the player hits a beat where the character opens their mouth and nothing comes out.

Open the event and fill in Body, or delete the event if it was scaffolding for a beat you decided not to write. See Branching dialogue.

5.2 Dialogue text is longer than the runtime cap

The Body text exceeds the runtime’s per-line cap. Studios that render the line in a fixed UI area may truncate the tail silently, so the panel flags it at authoring time instead of letting the player see half a line.

Trim the text, or split the line into two Dialogue events on adjacent nodes so the pacing carries the beat instead of one wall of text. See Branching dialogue.

5.3 Template text is longer than the runtime cap

A template string, for example a custom string authored in the localization table, exceeds the runtime’s cap after substitution. ICU placeholders can expand into names, numbers, or plural forms, so the cap is measured against a worst-case width for the substitutions.

Shorten the template, or split it into two strings if the placeholder expansions push the worst case over the cap. See Localization.

6. Skill checks

6.1 Skill check is nested inside another skill check

A Skill check event lives inside another Skill check’s outcome branch. Nested resolution has no defined semantics: the outer check would need to resolve before the inner one could roll, and the runtime has no rule for how the outer state feeds the inner roll.

Move the inner check onto a separate node reached by an inbound Jump from the outer check’s outcome branch. The outer check resolves first, then the flow hands off to the inner check on its own node. See Skill checks.

6.2 Skill check names an unknown skill

The event’s Skill field references a skill that isn’t in the project’s skills list. Usually a rename or delete on the skills side that didn’t propagate to every check that references it.

Open the event and pick a live skill from the dropdown, or add the missing skill to the project if the reference was correct and the skill went missing. See Skill checks.

6.3 Dice or critical-threshold values don’t fit the roll

The check’s Dice array is empty, has a non-positive side, or has a threshold that can’t fire. Any negative or zero die side is an error; so is a Critical fail threshold or Critical success threshold that isn’t a positive integer. A threshold whose sum falls outside the range the dice can roll surfaces as a warning (“can never fire”), because the outcome band it labels is unreachable. Threshold sums are compared against the dice range: for n dice the minimum roll is n and the maximum is the sum of the sides.

Open the event, or Project Settings for the project-wide default, and fix whichever the panel row names. For thresholds, keep each value between the number of dice and the maximum possible roll so the crit band can fire. See Skill checks.

6.4 Modifier is missing a required field

One of the check’s modifiers has no Expression, no Label, or a duplicate Id with another modifier on the same check. Modifiers need all three to render in the simulator’s breakdown and to resolve at runtime; a missing field silently drops the row.

Open the modifier row and fill in the missing field, or rename the duplicate id to something unique across the check’s modifier list. See Skill checks.

6.5 Skill check has a title, retry, or passive shape that doesn’t add up

Three shape warnings on the check itself, separate from the modifier rows in §6.4. Each fires independently and the panel row names which one it hit. Title is blank, so reports and logs read as “Unnamed check” (or “Unnamed skill check” when a skill is set) and the check is hard to spot in a run log. Max attempts is set but Retryable is off, so the cap has no effect because the check runs once regardless. Or Passive is on and the check has Critical fail events, so the player sees the consequences without ever seeing the check surface, which is usually a bug rather than an intent.

Fill in Title so the check names itself; turn on Retryable to match Max attempts, or clear Max attempts if the check isn’t meant to retry; and move critical-fail work off a passive check, or turn off Passive if the check should be visible after all. See Skill checks.

7. Activities

7.1 Activity group is missing a member

The group’s Members list references a variable that isn’t declared on the activity, or the same variable is listed in two groups on the same activity. Group membership is exclusive per activity, so double-membership has no defined aggregation.

Open the group inspector and either add the missing variable to the activity’s variables list, or remove the extra membership so each variable belongs to exactly one group. See Activity.

7.2 Activity group member isn’t a boolean

The group’s Members list includes an activity variable whose type isn’t bool. Groups aggregate boolean states, counting how many members are true or whether at least one is, so non-boolean members have no defined contribution.

Change the variable’s Type to bool on the activity, or drop it from the group’s Members list. See Activity.

7.3 Activity group bounds or override are invalid

Either the group’s Min or Max count-of-true bounds are impossible (min above max, or bounds outside the member count), or a per-event override references an activity variable that isn’t on the activity. The runtime can’t aggregate against bounds it can’t satisfy or override a variable it can’t find.

Correct the bounds so Min ≤ Max ≤ member count, or fix the override to reference a variable declared on the activity. See Activity.

7.4 Read-only activity variable has a default

An activity variable with Read-only on also declares a Default value, or an event override sets a Default on a read-only activity variable. Read-only means the value is fixed at declaration; a default is meaningless in that context and the panel flags the contradiction.

Either turn Read-only off if you need the default to take effect, or remove the default and let the variable’s declared value stand. See Activity.

8. Audio and media

8.1 Audio cue has no segments, or a segment has no refs

The audio cue has no segments, a segment has no media refs, a segment references a media entity that isn’t audio, or the cue exists but no event plays it (Warning for the last). The runtime can’t play a cue with no playable segments; the unused-cue Warning helps keep the media list clean.

Open the cue and add segments and audio media refs, or delete the cue if it was scaffolding. For the unused-cue Warning, wire the cue into a Play cue event or delete it. See Audio cues.

8.2 Custom-layer audio cue needs a layer name

The cue’s or event’s Layer is set to Custom, but Custom layer is empty. The same shape applies to Custom scope: the field is set to Custom with no name filled in. The runtime routes cues by layer name and needs a value to route them.

Open the cue or Play cue event and fill in Custom layer (and Custom scope if the scope is custom too), or switch the layer to one of the built-in layers so the custom-name field isn’t required. See Audio cues.

8.3 Ambience cue reference is missing or wrong type

A location’s ambience references a cue that no longer exists, references a cue whose layer isn’t Ambience, or a Play cue event references a cue that no longer exists. Ambience needs a cue of the right layer to play, and a deleted cue leaves a dangling reference.

Open the location or event and repoint the ref at a live cue of the right layer, or clear the ref if the ambience is gone. See Audio cues.

8.4 Audio timing anchor can’t be resolved

A timing anchor points at an event id that isn’t in the current scope, sits at an offset that exceeds the referenced clip’s duration, or crosses an activity boundary. The runtime schedules segments relative to an anchor event, so when the anchor can’t be found in scope the segment has no start time.

Open the segment and repoint the anchor at an event in the same scope, or trim the offset so it lands inside the clip’s duration. See Audio cues.

8.5 Audio segment loop shape is invalid

The segment sets Loop until audio ends on a non-looping source, or sets Random each loop without turning Loop on. Both are loop-mode combinations the runtime has no defined behavior for, because one flag expects a loop and the other rules one out.

Open the segment and either turn Loop on to match the sibling flag, or clear the flag that expects a looping shape. See Audio cues.

8.6 Background-video clip bounds are invalid

The background-video clip has a negative Start, an End that isn’t strictly after Start, or an End past the source video’s duration. The runtime can’t play a clip with impossible bounds and the media list refuses to bind it.

Open the media entity and set Start to zero or above, End strictly after Start, and End no later than the video’s duration. See Media.

9. Loading, export, and locale

9.1 Folder isn’t a bonsai project

The folder picked from File → Open doesn’t contain a bonsai.json at its root. Either the wrong folder was picked, or the folder is a partial checkout missing the project marker file. The blocking dialog names the picked path.

Pick the folder that contains bonsai.json at its root, or verify the checkout is complete if the folder should have contained it. See Editor tour.

9.2 Export is blocked by unresolved errors

File → Export runtime JSON only runs when the panel shows zero Errors. Warnings are allowed through; Errors block the export because a project shipping with Errors can hit runtime states no downstream code can recover from silently. The dialog names the first blocking issue.

Open the panel, resolve every Error row, then re-run the export. Warnings can be triaged after the export if they’re intentional placeholders. See Menu and shortcuts.

9.3 Structural changes are locked to the source locale

The Active locale dropdown is on a translation locale, not the source. Structural changes such as creating or deleting entities and deleting nodes can only be made in the source locale, so translation locales stay a strict overlay. The renderer guards the change and shows a toast instead of applying it.

Open the locale dropdown, switch back to the source locale, then make the structural change. The overlay for the translation locale updates on the next re-save. See Localization.

9.4 Time-of-day feature is disabled

An event references time-of-day (an advance-time event or a gate on time-of-day) but Time is off. The project has time-of-day gated off that the event tree still uses, so the event silently no-ops at runtime.

Either open Project → Time and turn Time-of-day on, or remove the time-of-day event from the flagged node. Both restore consistency between the project’s feature settings and the events it fires.

9.5 The runtime crashed

The AOT subprocess that owns validation, path testing, and simulation exited unexpectedly. Rare; usually a bug in the runtime rather than a state you can fix by editing. The banner sits across the top of the editor until the subprocess restarts or the editor closes.

Save any unsaved work if the editor still responds, then restart the editor. The banner may say recovered if the subprocess restarted itself; a full editor restart is the safest reset regardless of what the banner says.

9.6 Localized variable compared against a string literal

An expression compares a Localized string variable against a quoted literal (var == "raw text", var != "raw text", or Contains(var, "raw text")). Localized variables hold a translation key resolved to the player’s active locale, so a literal-string comparison passes only in the source locale and silently fails in every translation. The dialog names the flagged variable and the callsite.

Wrap the literal with Localize("<string_id>") so the comparison is against the same translated form the variable will resolve to, or turn Localized off on the variable if the comparison is against a literal state token that shouldn’t be translated at all. See Localization.

Docs last synced: 2026-07-08
Screenshot viewer