cayleygraph/cayley · error

expected string, got: %T

Error message

expected string, got: %T

What it means

save's optional second argument is the output tag and must be a string; save narrows it with a Go type assertion and throws through the VM when it is anything else. The first argument is the predicate/path to save; the second names the key under which results appear.

Source

Thrown at query/gizmo/traversals.go:462

	if rev {
		np = np.HasReverse(via, qv...)
	} else {
		np = np.Has(via, qv...)
	}
	return p.newVal(np)
}
func (p *pathObject) save(call goja.FunctionCall, rev, opt bool) goja.Value {
	args := exportArgs(call.Arguments)
	if len(args) > 2 || len(args) == 0 {
		return throwErr(p.s.vm, errArgCount{Got: len(args)})
	}
	var vtag interface{} = ""
	if len(args) == 2 {
		vtag = args[1]
	}
	tag, ok := vtag.(string)
	if !ok {
		return throwErr(p.s.vm, fmt.Errorf("expected string, got: %T", vtag))
	}
	via := args[0]
	if vp, ok := via.(*pathObject); ok {
		via = vp.path
		if tag == "" {
			return throwErr(p.s.vm, errors.New("must specify a tag name when saving a path"))
		}
	} else {
		qv, err := toQuadValue(via)
		via = qv
		if err != nil {
			return throwErr(p.s.vm, err)
		}
		if tag == "" {
			if p.s.col == query.JSONLD {
				switch qv := qv.(type) {
				case quad.IRI:
					tag = string(qv)

View on GitHub (pinned to 81dcd7d73e)

Solutions

  1. Pass a plain string as the second argument: p.save("knows", "friendTag").
  2. Omit the second argument entirely to use the default tag.
  3. Coerce with String(x) in JS if the value may be non-string.

Example fix

// before
p.save("knows", 42)
// after
p.save("knows", "friend")
Defensive patterns

Strategy: type-guard

Validate before calling

if (vtag !== undefined && typeof vtag !== 'string') throw new Error('save tag must be a string');

Type guard

function isStringOrUndefined(v) { return v === undefined || typeof v === 'string'; }

Try / catch

try { p.save('knows', tag); } catch (e) { if (String(e).includes('expected string, got')) { tag = String(tag); } throw e; }

Prevention

When it happens

Trigger: Calling p.save("knows", 42), p.save(pred, {tag: ...}), or otherwise passing a non-string second argument that reaches query/gizmo/traversals.go:462.

Common situations: JS scripts passing a variable that is undefined/null or a number as the tag name, or accidentally reordering arguments so an object lands in the tag slot.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of cayleygraph/cayley@81dcd7d73e (2026-09-06). Data as JSON: /api/errors/9527679cf6a0df79. Report an issue: GitHub.