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 := 1

View on GitHub (pinned to a4447c1563)

Solutions

  1. Set the OID before insert (the project's ID generator, e.g. waveobj.GenOID())
  2. Assert oid != "" in your factory/helper that returns new wave objects
  3. 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

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


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/7de43016b678a4ed. Report an issue: GitHub.