cocoindex-io/cocoindex · error · ValueError

Invalid Neo4j {kind}: {name!r}. Must match [a-zA-Z_][a-zA-Z0

Error message

Invalid Neo4j {kind}: {name!r}. Must match [a-zA-Z_][a-zA-Z0-9_]*.

What it means

Cypher cannot parameter-bind labels, relationship types, property names, or index names — they must be interpolated into the query string. validate_identifier rejects any name not matching ^[a-zA-Z_][a-zA-Z0-9_]*$ at API entry, preventing both malformed Cypher and Cypher injection from untrusted names.

Source

Thrown at python/cocoindex/connectors/neo4j/_cypher.py:52

    "constraint_name",
    "index_name",
    "validate_identifier",
    "vector_index_name",
]


IDENTIFIER_RE = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*$")


def validate_identifier(name: str, kind: str) -> None:
    """Reject anything that isn't ``[a-zA-Z_][a-zA-Z0-9_]*``.

    Cypher labels, property names, and index names cannot be parameter-bound,
    so untrusted names must be validated at API entry — never escaped at query
    construction time.
    """
    if not IDENTIFIER_RE.match(name):
        raise ValueError(
            f"Invalid Neo4j {kind}: {name!r}. Must match [a-zA-Z_][a-zA-Z0-9_]*."
        )


def _quote(name: str) -> str:
    """Backtick-wrap an already-validated identifier for inline use in Cypher."""
    return f"`{name}`"


def _key_clause(prefix: str, fields: Sequence[str]) -> str:
    """Build ``{<f1>: $<prefix>_0, <f2>: $<prefix>_1, ...}`` for a MATCH/MERGE pattern."""
    parts = [f"{_quote(f)}: ${prefix}_{i}" for i, f in enumerate(fields)]
    return "{" + ", ".join(parts) + "}"


def index_name(kind: str, label: str, fields: Sequence[str]) -> str:
    """Deterministic index name for a (kind, label, fields) triple.

View on GitHub (pinned to e84aa99b32)

Solutions

  1. Rename the label/type/property to match [a-zA-Z_][a-zA-Z0-9_]* (replace '-' and '.' with '_')
  2. Sanitize/normalize dynamic names (e.g. re.sub(r'[^A-Za-z0-9_]', '_', name)) and reject empty results before calling the connector
  3. Use a fixed literal label in code instead of deriving it from untrusted input
  4. Validate early with the same regex (IDENTIFIER_RE) at config-load time

Example fix

// before
label = f"doc-{file_ext}"  # 'doc-pdf' fails validation
// after
import re
label = "doc_" + re.sub(r"[^A-Za-z0-9_]", "_", file_ext)
from cocoindex.connectors.neo4j._cypher import validate_identifier
validate_identifier(label, "label")  # passes
Defensive patterns

Strategy: validation

Validate before calling

import re
IDENTIFIER_RE = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*$")

def safe_label(name: str) -> str:
    if not IDENTIFIER_RE.match(name):
        raise ValueError(f"invalid Neo4j label: {name!r}")
    return name

Type guard

import re
_ID = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*$")
def is_valid_identifier(name) -> bool:
    return isinstance(name, str) and bool(_ID.match(name))

Try / catch

try:
    validate_identifier(label, "label")
except ValueError as e:
    logger.error("sanitize label before use: %s", e)
    label = re.sub(r"[^A-Za-z0-9_]", "_", label) or "_default"
    validate_identifier(label, "label")

Prevention

When it happens

Trigger: Passing a label, relationship type, property key, or index name containing characters outside [a-zA-Z_][a-zA-Z0-9_]* (spaces, hyphens, dots, backticks, unicode, or an empty string) to any Neo4j connector API that builds Cypher (node/relationship upsert, delete, index create).

Common situations: Label derived from a filename or table name containing hyphens ('my-table'); namespaced property names ('meta.title'); user-supplied labels; empty label after string slicing; non-ASCII labels.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


AI-assisted analysis of cocoindex-io/cocoindex@e84aa99b32 (2026-09-08). Data as JSON: /api/errors/5c475e6a1a41c6b2. Report an issue: GitHub.