temporalio/temporal · error
error reading contents: unmatched `END` keyword
Error message
error reading contents: unmatched `END` keyword
What it means
A bare `END` must close an open `BEGIN` block. When the parser encounters `END` (not followed by IF/LOOP) while its block stack is empty or topped by something other than BEGIN, it returns this error, since the schema script has unbalanced block keywords.
Source
Thrown at common/persistence/query_util.go:147
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 {
return nil, fmt.Errorf("error reading contents: unmatched quotes")
}
case sqlLineComment[0]:
if j+len(sqlLineComment) <= n && contentStr[j:j+len(sqlLineComment)] == sqlLineComment {
_, _ = stmtBuilder.Write(bytes.TrimRight(content[i:j], " "))View on GitHub (pinned to bde624efd1)
Solutions
- Fix any earlier unbalanced IF/LOOP blocks in the file first — a single early mismatch cascades into this error at the bare END
- Ensure the standalone END has a matching BEGIN above it; add or remove accordingly
- Diff against the original upstream schema file to locate the lost/extra BEGIN
Example fix
// before IF x THEN UPDATE t SET c = 1; END; // after IF x THEN UPDATE t SET c = 1; END IF;
Defensive patterns
Strategy: validation
Validate before calling
beginDepth := 0
for _, tok := range tokenizeUpper(content) {
switch tok {
case "BEGIN":
beginDepth++
case "END":
beginDepth--
if beginDepth < 0 {
return errors.New("bare END without matching BEGIN")
}
}
} Prevention
- First fix any earlier IF/LOOP imbalance — a desynced stack surfaces as an unmatched bare END
- Use END IF / END LOOP explicitly instead of bare END where the dialect allows
- Keep BEGIN/END pairing per procedure in one place and avoid splicing files from different dialects
When it happens
Trigger: Calling LoadAndSplitQuery/LoadAndSplitQueryFromReaders/parseSQLStmts on a schema file with a standalone `END;` that has no enclosing `BEGIN`, or where an earlier IF/END mismatch shifted the stack so BEGIN is not on top.
Common situations: An earlier unmatched END IF/END LOOP popping the wrong entry (or an IF closed by bare END), desynchronizing the stack so a legitimate END appears unmatched; truncation dropping a BEGIN; dialects like T-SQL where END pairs with BEGIN differently.
Related errors
- error reading contents: unmatched `END IF` keyword
- error reading contents: unmatched `END LOOP` 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/1ff13cce676c5e2f.
Report an issue: GitHub.