{"record":{"id":"f39f446ac5d55cd0","repo":"wavetermdev/waveterm","slug":"cannot-update-t-value-with-empty-id","errorCode":null,"errorMessage":"cannot update %T value with empty id","messagePattern":"cannot update %T value with empty id","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/wstore/wstore_dbops.go","lineNumber":304,"sourceCode":"\t\tdefer func() {\n\t\t\tpanichandler.PanicHandler(\"DBDelete:filestore.DeleteZone\", recover())\n\t\t}()\n\t\t// we spawn a go routine here because we don't want to reuse the DB connection\n\t\t// since DBDelete is called in a transaction from DeleteTab\n\t\tdeleteCtx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second)\n\t\tdefer cancelFn()\n\t\terr := filestore.WFS.DeleteZone(deleteCtx, id)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"error deleting filestore zone (after deleting block): %v\", err)\n\t\t}\n\t}()\n\treturn nil\n}\n\nfunc DBUpdate(ctx context.Context, val waveobj.WaveObj) error {\n\toid := waveobj.GetOID(val)\n\tif oid == \"\" {\n\t\treturn fmt.Errorf(\"cannot update %T value with empty id\", val)\n\t}\n\tjsonData, err := waveobj.ToJson(val)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn WithTx(ctx, func(tx *TxWrap) error {\n\t\ttable := waveObjTableName(val)\n\t\tquery := fmt.Sprintf(\"UPDATE %s SET data = ?, version = version+1 WHERE oid = ? RETURNING version\", table)\n\t\tnewVersion := tx.GetInt(query, jsonData, oid)\n\t\twaveobj.SetVersion(val, newVersion)\n\t\twaveobj.ContextAddUpdate(ctx, waveobj.WaveObjUpdate{UpdateType: waveobj.UpdateType_Update, OType: val.GetOType(), OID: oid, Obj: val})\n\t\treturn nil\n\t})\n}\n\nfunc DBUpdateFn[T waveobj.WaveObj](ctx context.Context, id string, updateFn func(T)) error {\n\treturn WithTx(ctx, func(tx *TxWrap) error {\n\t\tval, err := DBMustGet[T](tx.Context(), id)","sourceCodeStart":286,"sourceCodeEnd":322,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/wstore/wstore_dbops.go#L286-L322","documentation":"DBUpdate persists a WaveObj keyed by its OID. If the object's ID is empty there is no primary key to update, so the call fails fast with the concrete Go type named in the message. It guards against upserting orphan rows that could never be read back.","triggerScenarios":"Passing a newly-constructed WaveObj (Tab, Block, Window, etc.) with its OID field never set, or an object deserialized from JSON that lacked an oid field, into wstore.DBUpdate.","commonSituations":"Building a waveobj struct literal and forgetting to assign the generated ID; copying fields but not the OID between objects; a constructor that returns the zero value on an error path.","solutions":["Assign a valid OID (e.g. via waveobj.GenWaveOType/GenerateOID helpers used elsewhere) before calling DBUpdate","Use DBInsert instead of DBUpdate if the object is new and has no ID yet","Check waveobj.GetOID(obj) != \"\" before persisting","Fix deserialization so the oid field is preserved when loading objects"],"exampleFix":"// before\nblk := &waveobj.Block{Meta: meta}\nerr := wstore.DBUpdate(ctx, blk)\n// after\nblk := &waveobj.Block{OID: waveobj.GenOID(), Meta: meta}\nerr := wstore.DBInsert(ctx, blk) // or set the existing OID before DBUpdate","handlingStrategy":"validation","validationCode":"if waveobj.GetOID(obj) == \"\" {\n\treturn fmt.Errorf(\"object %T missing oid before update\", obj)\n}","typeGuard":"func hasOID(w waveobj.WaveObj) bool { return waveobj.GetOID(w) != \"\" }","tryCatchPattern":"if err := wstore.DBUpdate(ctx, obj); err != nil {\n\tif strings.Contains(err.Error(), \"empty id\") {\n\t\treturn fmt.Errorf(\"%T was not assigned an id\", obj)\n\t}\n\treturn err\n}","preventionTips":["Generate OIDs in constructors, never at call sites","Never persist zero-value wave objects","Preserve the oid field through serialization/deserialization"],"tags":["validation","waveobj","primary-key"],"backgroundTag":"missing-object-id","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}