{"record":{"id":"d02fb378f989b291","repo":"etcd-io/etcd","slug":"unsupported-stm","errorCode":null,"errorMessage":"unsupported stm","messagePattern":"unsupported stm","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/v3/concurrency/stm.go","lineNumber":132,"sourceCode":"\t\t}\n\t\treturn s\n\tcase Serializable:\n\t\ts := &stmSerializable{\n\t\t\tstm:      stm{client: c, ctx: opts.ctx},\n\t\t\tprefetch: make(map[string]*v3.GetResponse),\n\t\t}\n\t\ts.conflicts = func() []v3.Cmp { return s.rset.cmps() }\n\t\treturn s\n\tcase RepeatableReads:\n\t\ts := &stm{client: c, ctx: opts.ctx, getOpts: []v3.OpOption{v3.WithSerializable()}}\n\t\ts.conflicts = func() []v3.Cmp { return s.rset.cmps() }\n\t\treturn s\n\tcase ReadCommitted:\n\t\ts := &stm{client: c, ctx: opts.ctx, getOpts: []v3.OpOption{v3.WithSerializable()}}\n\t\ts.conflicts = func() []v3.Cmp { return nil }\n\t\treturn s\n\tdefault:\n\t\tpanic(\"unsupported stm\")\n\t}\n}\n\ntype stmResponse struct {\n\tresp *v3.TxnResponse\n\terr  error\n}\n\nfunc runSTM(s STM, apply func(STM) error) (*v3.TxnResponse, error) {\n\toutc := make(chan stmResponse, 1)\n\tgo func() {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\te, ok := r.(stmError)\n\t\t\t\tif !ok {\n\t\t\t\t\t// client apply panicked\n\t\t\t\t\tpanic(r)\n\t\t\t\t}","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/etcd-io/etcd/blob/f744d457f484e9f748a0700b48ef96dcf792df33/client/v3/concurrency/stm.go#L114-L150","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\niso := concurrency.Isolation(cfg.IsolationLevelInt) // cfg says 9\nconcurrency.NewSTM(cli, apply, concurrency.WithIsolation(iso)) // panics: unsupported stm\n\n// after\nlevels := map[string]concurrency.Isolation{\n    \"serializable-snapshot\": concurrency.SerializableSnapshot,\n    \"serializable\": concurrency.Serializable,\n    \"repeatable-reads\": concurrency.RepeatableReads,\n    \"read-committed\": concurrency.ReadCommitted,\n}\niso, ok := levels[cfg.IsolationLevel]\nif !ok {\n    return fmt.Errorf(\"unknown isolation level %q\", cfg.IsolationLevel)\n}\nconcurrency.NewSTM(cli, apply, concurrency.WithIsolation(iso))","handlingStrategy":"validation","validationCode":"func parseIsolation(s string) (concurrency.Isolation, error) {\n\tswitch s {\n\tcase \"serializable-snapshot\", \"\":\n\t\treturn concurrency.SerializableSnapshot, nil\n\tcase \"serializable\":\n\t\treturn concurrency.Serializable, nil\n\tcase \"repeatable-reads\":\n\t\treturn concurrency.RepeatableReads, nil\n\tcase \"read-committed\":\n\t\treturn concurrency.ReadCommitted, nil\n\tdefault:\n\t\treturn 0, fmt.Errorf(\"unknown isolation level %q\", s)\n\t}\n}","typeGuard":"func isValidIsolation(l concurrency.Isolation) bool {\n\treturn l >= concurrency.SerializableSnapshot && l <= concurrency.ReadCommitted\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["go","etcd","clientv3","concurrency","stm","panic","invalid-argument","config"],"backgroundTag":null,"analyzedSha":"f744d457f484e9f748a0700b48ef96dcf792df33","analyzedAt":"2026-08-15T09:39:50.079Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}