stride3d/stride · error · InvalidOperationException

Property path parse error: could not find indexer end ']'

Error message

Property path parse error: could not find indexer end ']'

What it means

UpdateEngine.Compile tokenizes a property path (used by animations, scripts, and the property updater) and, when it detects an indexer segment ('['), searches for the matching ']'. If the closing bracket is missing, it throws InvalidOperationException because the path is syntactically incomplete.

Solutions

  1. Complete the path so every '[' has a matching ']': 'Items[0]' not 'Items[0'.
  2. Validate the path with a parser/regex before calling Compile.
  3. Escape or strip stray '[' characters that are not intended as indexers.
  4. Log the full animation path and member names when compilation fails to spot truncation.

Example fix

// before
var path = "Materials[0"; // missing ']'
// after
var path = "Materials[0]";
Defensive patterns

Strategy: validation

Validate before calling

bool hasBalancedIndexers(string path) => path.Count(c => c == '[') == path.Count(c => c == ']');

Try / catch

try { engine.Compile(target, path, ...); } catch (InvalidOperationException ex) when (ex.Message.Contains("indexer end")) { log.Error($"Unclosed '[' in path: {path}"); }

Prevention

When it happens

Trigger: Calling Compile with a path containing '[' but no ']', e.g. 'Transform.Position[' or 'Items[0'. The IndexOf(PathIndexerClose) returns -1 and the error is thrown.

Common situations: Truncated property paths from string concatenation; paths copied incompletely from editors/logs; animation key targets generated from UI text that lost the closing bracket.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/d02070d58b8e7a7a. Report an issue: GitHub.

Appendix: source

Thrown at sources/engine/Stride.Engine/Updater/UpdateEngine.cs:189

                // Parse the new path parts
                state.ParseElementStart = state.StackPath.Last().EndIndex;

                // Compute offset from start of current object
                state.NewOffset = state.StackPath.Last().ObjectStartOffset;

                while (state.ParseElementStart < animationPath.Name.Length)
                {
                    var containerType = state.StackPath.Last().Type;

                    // We have only two cases for now: array or property/field name
                    bool isIndexerAccess = animationPath.Name[state.ParseElementStart] == PathIndexerOpen;
                    if (isIndexerAccess)
                    {
                        // Parse until end of indexer
                        state.ParseElementEnd = animationPath.Name.IndexOf(PathIndexerClose, state.ParseElementStart + 1);
                        if (state.ParseElementEnd == -1)
                            throw new InvalidOperationException("Property path parse error: could not find indexer end ']'");

                        // Include the indexer close
                        state.ParseElementEnd++;

                        // Parse integer
                        // TODO: Avoid substring?
                        var indexerString = animationPath.Name.Substring(state.ParseElementStart + 1, state.ParseElementEnd - state.ParseElementStart - 2);

                        // T[], IList<T>, etc...
                        // Try to resolve using custom resolver
                        UpdatableMember updatableMember = null;
                        var parentType = containerType;
                        while (parentType != null)
                        {
                            UpdateMemberResolver resolver;
                            if (MemberResolvers.TryGetValue(parentType, out resolver))
                            {
                                updatableMember = resolver.ResolveIndexer(indexerString);

View on GitHub (pinned to 96fad776d2)