temporalio/temporal · error
error reading contents: unmatched `END LOOP` keyword
Error message
error reading contents: unmatched `END LOOP` keyword
What it means
During statement splitting, the parser maintains a stack of BEGIN/IF/LOOP blocks. Reaching `END LOOP` when the stack is empty or the top entry is not LOOP means the schema text has an END LOOP without a matching LOOP, so LoadAndSplitQueryFromReaders aborts with this error.
Source
Thrown at common/persistence/query_util.go:141
if hasWordAt(contentStr, sqlBeginKeyword, j) {
st = append(st, sqlBeginKeyword[0])
j += len(sqlBeginKeyword) - 1
}
case sqlEndKeyword[0]:
if !hasWordAt(contentStr, sqlEndKeyword, j) {
continue
}
if ok, after := hasWordAfter(contentStr, sqlIfKeyword, j+len(sqlEndKeyword)); ok {
if len(st) == 0 || st[len(st)-1] != sqlIfKeyword[0] {
return nil, errors.New("error reading contents: unmatched `END IF` keyword")
}
st = st[:len(st)-1]
j = after + len(sqlIfKeyword) - 1
} else if ok, after := hasWordAfter(contentStr, sqlLoopKeyword, j+len(sqlEndKeyword)); ok {
//nolint:revive
if len(st) == 0 || st[len(st)-1] != sqlLoopKeyword[0] {
return nil, errors.New("error reading contents: unmatched `END LOOP` keyword")
}
st = st[:len(st)-1]
j = after + len(sqlLoopKeyword) - 1
} else {
if len(st) == 0 || st[len(st)-1] != sqlBeginKeyword[0] {
return nil, errors.New("error reading contents: unmatched `END` keyword")
}
st = st[:len(st)-1]
j += len(sqlEndKeyword) - 1
}
case sqlSingleQuote, sqlDoubleQuote:
quote := contentStr[j]
j++
for j < n && contentStr[j] != quote {
j++
}
if j == n {View on GitHub (pinned to bde624efd1)
Solutions
- Locate the END LOOP in the schema file and ensure a matching LOOP appears earlier at the same nesting depth
- Restore the missing LOOP block or delete the stray END LOOP
- Verify nesting order (BEGIN > IF/LOOP) is properly interleaved; reorder END statements so each END matches the innermost open block
Example fix
// before
BEGIN
INSERT ...
END LOOP;
// after
BEGIN
FOR r IN list LOOP
INSERT ...;
END LOOP;
END;
Defensive patterns
Strategy: validation
Validate before calling
depth := 0
for _, tok := range tokenizeUpper(content) {
switch tok {
case "LOOP":
depth++
case "END LOOP":
depth--
if depth < 0 {
return errors.New("END LOOP without matching LOOP")
}
}
} Prevention
- Ensure each END LOOP is preceded by LOOP at the same nesting level
- Close nested blocks in reverse order of opening (LIFO)
- Run schema files through the splitter in CI to catch balance errors early
When it happens
Trigger: Calling LoadAndSplitQuery/LoadAndSplitQueryFromReaders on a schema file containing `END LOOP` whose corresponding `LOOP` was never opened (or was closed early/nested incorrectly).
Common situations: Copied CQL from another database dialect where LOOP syntax differs; a LOOP removed during editing leaving its END LOOP; nested BEGIN/LOOP pairs closed in the wrong order causing stack mismatch.
Related errors
- error reading contents: unmatched `END IF` keyword
- error reading contents: unmatched `END` keyword
- unable to insert workflow execution: %w
- unable to insert custom search attributes: %w
- unable to insert chasm search attributes: %w
AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01).
Data as JSON: /api/errors/41814d31e266714a.
Report an issue: GitHub.