{"record":{"id":"9527679cf6a0df79","repo":"cayleygraph/cayley","slug":"expected-string-got-t-952767","errorCode":null,"errorMessage":"expected string, got: %T","messagePattern":"expected string, got: %T","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"query/gizmo/traversals.go","lineNumber":462,"sourceCode":"\tif rev {\n\t\tnp = np.HasReverse(via, qv...)\n\t} else {\n\t\tnp = np.Has(via, qv...)\n\t}\n\treturn p.newVal(np)\n}\nfunc (p *pathObject) save(call goja.FunctionCall, rev, opt bool) goja.Value {\n\targs := exportArgs(call.Arguments)\n\tif len(args) > 2 || len(args) == 0 {\n\t\treturn throwErr(p.s.vm, errArgCount{Got: len(args)})\n\t}\n\tvar vtag interface{} = \"\"\n\tif len(args) == 2 {\n\t\tvtag = args[1]\n\t}\n\ttag, ok := vtag.(string)\n\tif !ok {\n\t\treturn throwErr(p.s.vm, fmt.Errorf(\"expected string, got: %T\", vtag))\n\t}\n\tvia := args[0]\n\tif vp, ok := via.(*pathObject); ok {\n\t\tvia = vp.path\n\t\tif tag == \"\" {\n\t\t\treturn throwErr(p.s.vm, errors.New(\"must specify a tag name when saving a path\"))\n\t\t}\n\t} else {\n\t\tqv, err := toQuadValue(via)\n\t\tvia = qv\n\t\tif err != nil {\n\t\t\treturn throwErr(p.s.vm, err)\n\t\t}\n\t\tif tag == \"\" {\n\t\t\tif p.s.col == query.JSONLD {\n\t\t\t\tswitch qv := qv.(type) {\n\t\t\t\tcase quad.IRI:\n\t\t\t\t\ttag = string(qv)","sourceCodeStart":444,"sourceCodeEnd":480,"githubUrl":"https://github.com/cayleygraph/cayley/blob/81dcd7d73e45136bc0d01802a8ba4685d8a533eb/query/gizmo/traversals.go#L444-L480","documentation":"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.","triggerScenarios":"Calling p.save(\"knows\", 42), p.save(pred, {tag: ...}), or otherwise passing a non-string second argument that reaches query/gizmo/traversals.go:462.","commonSituations":"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.","solutions":["Pass a plain string as the second argument: p.save(\"knows\", \"friendTag\").","Omit the second argument entirely to use the default tag.","Coerce with String(x) in JS if the value may be non-string."],"exampleFix":"// before\np.save(\"knows\", 42)\n// after\np.save(\"knows\", \"friend\")","handlingStrategy":"type-guard","validationCode":"if (vtag !== undefined && typeof vtag !== 'string') throw new Error('save tag must be a string');","typeGuard":"function isStringOrUndefined(v) { return v === undefined || typeof v === 'string'; }","tryCatchPattern":"try { p.save('knows', tag); } catch (e) { if (String(e).includes('expected string, got')) { tag = String(tag); } throw e; }","preventionTips":["Coerce tag variables with String() before calling save","Omit the second argument to use the default tag","Keep argument order: predicate first, tag second"],"tags":["gizmo","query","type-mismatch"],"backgroundTag":"type-mismatch","analyzedSha":"81dcd7d73e45136bc0d01802a8ba4685d8a533eb","analyzedAt":"2026-09-06T06:14:12.358Z","contentChangedAt":"2026-09-06T06:14:12.358Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}