{"record":{"id":"986906da471790d6","repo":"d2lang/d2","slug":"cannot-connect-to-reserved-keyword","errorCode":null,"errorMessage":"cannot connect to reserved keyword","messagePattern":"cannot connect to reserved keyword","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"d2graph/d2graph.go","lineNumber":1327,"sourceCode":"\t\t}\n\t\tcommonIDA = append(commonIDA, srcIDA[0])\n\t\tsrcIDA = srcIDA[1:]\n\t\tdstIDA = dstIDA[1:]\n\t}\n\n\tcommonKey := \"\"\n\tif len(commonIDA) > 0 {\n\t\tcommonKey = strings.Join(commonIDA, \".\") + \".\"\n\t}\n\n\treturn fmt.Sprintf(\"%s(%s %s %s)[%d]\", commonKey, strings.Join(srcIDA, \".\"), e.ArrowString(), strings.Join(dstIDA, \".\"), e.Index)\n}\n\nfunc (obj *Object) Connect(srcID, dstID []d2ast.String, srcArrow, dstArrow bool, label string) (*Edge, error) {\n\tfor _, id := range [][]d2ast.String{srcID, dstID} {\n\t\tfor _, p := range id {\n\t\t\tif _, ok := d2ast.ReservedKeywords[p.ScalarString()]; ok && p.IsUnquoted() {\n\t\t\t\treturn nil, errors.New(\"cannot connect to reserved keyword\")\n\t\t\t}\n\t\t}\n\t}\n\n\tsrc := obj.ensureChildEdge(srcID)\n\tdst := obj.ensureChildEdge(dstID)\n\n\te := &Edge{\n\t\tAttributes: Attributes{\n\t\t\tLabel: Scalar{\n\t\t\t\tValue: label,\n\t\t\t},\n\t\t},\n\t\tSrc:      src,\n\t\tSrcArrow: srcArrow,\n\t\tDst:      dst,\n\t\tDstArrow: dstArrow,\n\t}","sourceCodeStart":1309,"sourceCodeEnd":1345,"githubUrl":"https://github.com/d2lang/d2/blob/0d69dca6f532ceaeacd615d35d1eaa41a238ffdb/d2graph/d2graph.go#L1309-L1345","documentation":"Object.Connect in d2graph refuses to create an edge whose source or destination id segment is an unquoted d2 reserved keyword (like `shape`, `label`, `style`, etc.). Reserved keywords have special meaning in keys, so using one unquoted as an endpoint is ambiguous and rejected.","triggerScenarios":"Calling obj.Connect (directly or via d2oracle Set) where srcID or dstID contains an unquoted segment matching d2ast.ReservedKeywords, e.g. connecting to an object literally named `label` without quoting.","commonSituations":"Naming nodes after d2 keywords (`columns`, `rows`, `shape`, `label`) and then connecting them; programmatic graph building that doesn't quote identifiers.","solutions":["Rename the node to a non-reserved identifier.","If the name must stay, use a quoted string for that segment (d2ast String with IsUnquoted false) so it is treated literally.","Check the identifier against d2ast.ReservedKeywords before calling Connect in generated code."],"exampleFix":"// before\nconnect(src: \"label\", dst: \"a\")\n// after\nconnect(src: \"'label'\", dst: \"a\") // quoted, or rename the node","handlingStrategy":"validation","validationCode":"for _, seg := range []string{srcName, dstName} {\n  if _, reserved := d2ast.ReservedKeywords[seg]; reserved { seg = \"'\" + seg + \"'\" }\n}","typeGuard":"func isReservedUnquoted(seg d2ast.String) bool {\n  _, ok := d2ast.ReservedKeywords[seg.ScalarString()]\n  return ok && seg.IsUnquoted()\n}","tryCatchPattern":"if _, err := obj.Connect(srcID, dstID, false, false, \"\"); err != nil && err.Error() == \"cannot connect to reserved keyword\" {\n  // quote the offending segments and retry\n}","preventionTips":["Check endpoint names against d2ast.ReservedKeywords before connecting.","Avoid naming nodes with d2 keywords like shape, label, style.","Quote string identifiers programmatically when names come from user input."],"tags":["d2","reserved-keyword","validation"],"backgroundTag":"reserved-keyword-conflict","analyzedSha":"0d69dca6f532ceaeacd615d35d1eaa41a238ffdb","analyzedAt":"2026-08-31T12:19:21.182Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}