wavetermdev/waveterm · error

SetPath: Remove and CombineFn are mutually exclusive

Error message

SetPath: Remove and CombineFn are mutually exclusive

What it means

SetPath validates SetPathOpts before applying a path operation. Because Remove deletes the value at the path, it makes no sense to also supply CombineFn (which merges an existing value with a new one), so supplying both is rejected outright with this error. It is a pure API-misuse guard — the data is untouched.

Source

Thrown at pkg/ijson/ijson.go:234

type SetPathOpts struct {
	Budget    int // Budget 0 is unlimited (to set a 0 value, use -1)
	Force     bool
	Remove    bool
	CombineFn CombiningFunc
}

func SetPathNoErr(data any, path Path, value any, opts *SetPathOpts) any {
	ret, _ := SetPath(data, path, value, opts)
	return ret
}

func SetPath(data any, path Path, value any, opts *SetPathOpts) (any, error) {
	if opts == nil {
		opts = &SetPathOpts{}
	}
	if opts.Remove && opts.CombineFn != nil {
		return nil, fmt.Errorf("SetPath: Remove and CombineFn are mutually exclusive")
	}
	if opts.Remove && value != nil {
		return nil, fmt.Errorf("SetPath: Remove and value are mutually exclusive")
	}
	return setPathInternal(data, pathWithPos{Path: path, Index: 0}, value, *opts)
}

func checkAndModifyBudget(opts *SetPathOpts, pp pathWithPos, cost int) bool {
	if opts.Budget == 0 {
		return true
	}
	opts.Budget -= cost
	if opts.Budget < 0 {
		return false
	}
	if opts.Budget == 0 {
		// 0 is weird since it means unlimited, so we set it to -1 to fail the next operation
		opts.Budget = -1

View on GitHub (pinned to a4447c1563)

Solutions

  1. Remove CombineFn from the options when doing a delete, keeping only Remove: true.
  2. Remove the Remove flag when you intend to merge with CombineFn.
  3. If options come from user flags, validate exclusivity at the CLI/config layer before calling SetPath.

Example fix

// before
opts := &ijson.SetPathOpts{Remove: true, CombineFn: combineFn}
_, err := ijson.SetPath(data, path, nil, opts) // mutually exclusive
// after
opts := &ijson.SetPathOpts{Remove: true} // delete: no CombineFn, no value
_, err := ijson.SetPath(data, path, nil, opts)
Defensive patterns

Strategy: validation

Validate before calling

func safeOpts(opts *ijson.SetPathOpts) error {
    if opts != nil && opts.Remove && opts.CombineFn != nil {
        return errors.New("Remove and CombineFn cannot be used together")
    }
    return nil
}
if err := safeOpts(opts); err != nil { return err }

Try / catch

result, err := ijson.SetPath(data, path, value, opts)
if err != nil {
    if strings.Contains(err.Error(), "mutually exclusive") {
        opts.CombineFn = nil // fall back to plain removal
        result, err = ijson.SetPath(data, path, nil, opts)
    }
    if err != nil { return err }
}

Prevention

When it happens

Trigger: Calling SetPath with opts where opts.Remove == true AND opts.CombineFn != nil, e.g. ijson.SetPath(data, path, nil, &ijson.SetPathOpts{Remove: true, CombineFn: func(a, b any) any {...}}). Callers SetPathNoErr, ApplyCommand, and TestSetPath funnel through here so any of them can surface it.

Common situations: Options structs assembled dynamically (flags/config) where Remove and a combine callback are both set by accident; copy-pasted option literals extended with Remove without removing CombineFn.

Related errors


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