wavetermdev/waveterm · error
cannot insert %T value with empty id
Error message
cannot insert %T value with empty id
What it means
DBInsert writes a new WaveObj row keyed by its OID. An empty ID would produce an unusable row, so the call is rejected immediately with the offending Go type in the message. Every stored wave object must carry a non-empty unique identifier.
Source
Thrown at pkg/wstore/wstore_dbops.go:348
func DBUpdateFnErr[T waveobj.WaveObj](ctx context.Context, id string, updateFn func(T) error) error {
return WithTx(ctx, func(tx *TxWrap) error {
val, err := DBMustGet[T](tx.Context(), id)
if err != nil {
return err
}
err = updateFn(val)
if err != nil {
return err
}
return DBUpdate(tx.Context(), val)
})
}
func DBInsert(ctx context.Context, val waveobj.WaveObj) error {
oid := waveobj.GetOID(val)
if oid == "" {
return fmt.Errorf("cannot insert %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)
waveobj.SetVersion(val, 1)
query := fmt.Sprintf("INSERT INTO %s (oid, version, data) VALUES (?, ?, ?)", table)
tx.Exec(query, oid, 1, jsonData)
waveobj.ContextAddUpdate(ctx, waveobj.WaveObjUpdate{UpdateType: waveobj.UpdateType_Update, OType: val.GetOType(), OID: oid, Obj: val})
return nil
})
}
func DBFindTabForBlockId(ctx context.Context, blockId string) (string, error) {
return WithTxRtn(ctx, func(tx *TxWrap) (string, error) {
iterNum := 1View on GitHub (pinned to a4447c1563)
Solutions
- Set the OID before insert (the project's ID generator, e.g. waveobj.GenOID())
- Assert oid != "" in your factory/helper that returns new wave objects
- Use a constructor function that always assigns an ID instead of raw struct literals
Example fix
// before
ws := &waveobj.Workspace{Name: "default"}
wstore.DBInsert(ctx, ws)
// after
ws := &waveobj.Workspace{OID: waveobj.GenOID(), Name: "default"}
wstore.DBInsert(ctx, ws) Defensive patterns
Strategy: validation
Validate before calling
if waveobj.GetOID(obj) == "" {
obj.SetOID(waveobj.GenOID())
} Type guard
func insertable(w waveobj.WaveObj) bool { return waveobj.GetOID(w) != "" } Try / catch
if err := wstore.DBInsert(ctx, obj); err != nil {
if strings.Contains(err.Error(), "cannot insert") {
return fmt.Errorf("factory returned %T without id", obj)
}
return err
} Prevention
- Assign waveobj.GenOID() when constructing new objects
- Centralize object creation in factories that always set IDs
- Unit-test that factories never return zero-value objects
When it happens
Trigger: Calling wstore.DBInsert with a WaveObj whose OID was never generated — e.g. a struct literal, an error path returning a zero value, or a caller in StartJob/CreateClient/InitMainServer-style flows building the object before ID assignment.
Common situations: Forgetting waveobj.GenOID() (or equivalent) on new objects; refactoring a constructor so ID assignment was dropped; creating objects from templates that omit the oid field.
Related errors
- cannot update %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/7de43016b678a4ed.
Report an issue: GitHub.