wavetermdev/waveterm · error
cannot update %T value with empty id
Error message
cannot update %T value with empty id
What it means
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.
Source
Thrown at pkg/wstore/wstore_dbops.go:304
defer func() {
panichandler.PanicHandler("DBDelete:filestore.DeleteZone", recover())
}()
// we spawn a go routine here because we don't want to reuse the DB connection
// since DBDelete is called in a transaction from DeleteTab
deleteCtx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second)
defer cancelFn()
err := filestore.WFS.DeleteZone(deleteCtx, id)
if err != nil {
log.Printf("error deleting filestore zone (after deleting block): %v", err)
}
}()
return nil
}
func DBUpdate(ctx context.Context, val waveobj.WaveObj) error {
oid := waveobj.GetOID(val)
if oid == "" {
return fmt.Errorf("cannot update %T value with empty id", val)
}
jsonData, err := waveobj.ToJson(val)
if err != nil {
return err
}
return WithTx(ctx, func(tx *TxWrap) error {
table := waveObjTableName(val)
query := fmt.Sprintf("UPDATE %s SET data = ?, version = version+1 WHERE oid = ? RETURNING version", table)
newVersion := tx.GetInt(query, jsonData, oid)
waveobj.SetVersion(val, newVersion)
waveobj.ContextAddUpdate(ctx, waveobj.WaveObjUpdate{UpdateType: waveobj.UpdateType_Update, OType: val.GetOType(), OID: oid, Obj: val})
return nil
})
}
func DBUpdateFn[T waveobj.WaveObj](ctx context.Context, id string, updateFn func(T)) error {
return WithTx(ctx, func(tx *TxWrap) error {
val, err := DBMustGet[T](tx.Context(), id)View on GitHub (pinned to a4447c1563)
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
Example fix
// before
blk := &waveobj.Block{Meta: meta}
err := wstore.DBUpdate(ctx, blk)
// after
blk := &waveobj.Block{OID: waveobj.GenOID(), Meta: meta}
err := wstore.DBInsert(ctx, blk) // or set the existing OID before DBUpdate Defensive patterns
Strategy: validation
Validate before calling
if waveobj.GetOID(obj) == "" {
return fmt.Errorf("object %T missing oid before update", obj)
} Type guard
func hasOID(w waveobj.WaveObj) bool { return waveobj.GetOID(w) != "" } Try / catch
if err := wstore.DBUpdate(ctx, obj); err != nil {
if strings.Contains(err.Error(), "empty id") {
return fmt.Errorf("%T was not assigned an id", obj)
}
return err
} Prevention
- Generate OIDs in constructors, never at call sites
- Never persist zero-value wave objects
- Preserve the oid field through serialization/deserialization
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- cannot insert %T value with empty id
- cannot convert %T to %s (idx %d) error: %v
- cannot convert %T to %s (key %s) error: %v
- empty object reference
- Image too large (>5MB)
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/f39f446ac5d55cd0.
Report an issue: GitHub.