etcd-io/etcd · error
unsupported stm
Error message
unsupported stm
What it means
concurrency.NewSTM panics with "unsupported stm" in mkSTM when the configured Isolation value does not match any of the four supported levels: SerializableSnapshot, Serializable, RepeatableReads, ReadCommitted. Since Isolation is an unexported-constant int type, any value outside 0-3 — typically produced by casting an arbitrary integer or defining a custom level — hits the default branch.
Source
Thrown at client/v3/concurrency/stm.go:132
}
return s
case Serializable:
s := &stmSerializable{
stm: stm{client: c, ctx: opts.ctx},
prefetch: make(map[string]*v3.GetResponse),
}
s.conflicts = func() []v3.Cmp { return s.rset.cmps() }
return s
case RepeatableReads:
s := &stm{client: c, ctx: opts.ctx, getOpts: []v3.OpOption{v3.WithSerializable()}}
s.conflicts = func() []v3.Cmp { return s.rset.cmps() }
return s
case ReadCommitted:
s := &stm{client: c, ctx: opts.ctx, getOpts: []v3.OpOption{v3.WithSerializable()}}
s.conflicts = func() []v3.Cmp { return nil }
return s
default:
panic("unsupported stm")
}
}
type stmResponse struct {
resp *v3.TxnResponse
err error
}
func runSTM(s STM, apply func(STM) error) (*v3.TxnResponse, error) {
outc := make(chan stmResponse, 1)
go func() {
defer func() {
if r := recover(); r != nil {
e, ok := r.(stmError)
if !ok {
// client apply panicked
panic(r)
}View on GitHub (pinned to f744d457f4)
Solutions
- Pass only the exported constants: concurrency.WithIsolation(concurrency.SerializableSnapshot) (or Serializable, RepeatableReads, ReadCommitted).
- If the level comes from configuration, validate it maps to one of the four constants before calling NewSTM; reject unknown values with a normal error.
- Omit WithIsolation entirely to get the default SerializableSnapshot.
Example fix
// before
iso := concurrency.Isolation(cfg.IsolationLevelInt) // cfg says 9
concurrency.NewSTM(cli, apply, concurrency.WithIsolation(iso)) // panics: unsupported stm
// after
levels := map[string]concurrency.Isolation{
"serializable-snapshot": concurrency.SerializableSnapshot,
"serializable": concurrency.Serializable,
"repeatable-reads": concurrency.RepeatableReads,
"read-committed": concurrency.ReadCommitted,
}
iso, ok := levels[cfg.IsolationLevel]
if !ok {
return fmt.Errorf("unknown isolation level %q", cfg.IsolationLevel)
}
concurrency.NewSTM(cli, apply, concurrency.WithIsolation(iso)) Defensive patterns
Strategy: validation
Validate before calling
func parseIsolation(s string) (concurrency.Isolation, error) {
switch s {
case "serializable-snapshot", "":
return concurrency.SerializableSnapshot, nil
case "serializable":
return concurrency.Serializable, nil
case "repeatable-reads":
return concurrency.RepeatableReads, nil
case "read-committed":
return concurrency.ReadCommitted, nil
default:
return 0, fmt.Errorf("unknown isolation level %q", s)
}
} Type guard
func isValidIsolation(l concurrency.Isolation) bool {
return l >= concurrency.SerializableSnapshot && l <= concurrency.ReadCommitted
} Prevention
- Only pass the four exported constants; never construct Isolation from a raw integer without range validation.
- Default to omitting WithIsolation (SerializableSnapshot) unless you specifically need weaker isolation.
- Validate config-derived levels at load time so the process fails with a real error, not a panic.
When it happens
Trigger: concurrency.NewSTM(cli, apply, concurrency.WithIsolation(concurrency.Isolation(7))) or Isolation(-1), or reading the level from a config file/int flag and casting it directly. Constructing stmOptions manually with an unset-but-nonzero iso field is another path.
Common situations: Mapping a user-configurable isolation string/number to the Isolation type without validating the range; refactoring that renumbers or removes constants; copy-pasting a level constant from a different etcd version or another library.
Related errors
- Unknown result op
- bad compare value
- bad value %v of type %T
- unexpected revision = 0. Calling SyncUpdates before SyncBase
- `WithPrefix` and `WithFromKey` cannot be set at the same tim
AI-assisted analysis of etcd-io/etcd@f744d457f4 (2026-08-15).
Data as JSON: /api/errors/d02fb378f989b291.
Report an issue: GitHub.