{"record":{"id":"3bc3eb2dbeb11728","repo":"d2lang/d2","slug":"cannot-generate-unique-key-for-edge-chain","errorCode":null,"errorMessage":"cannot generate unique key for edge chain","messagePattern":"cannot generate unique key for edge chain","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"d2oracle/edit.go","lineNumber":2659,"sourceCode":"\tdeltas[edge.AbsID()] = newEdge.AbsID()\n\n\treturn deltas, nil\n}\n\n// generateUniqueKey generates a unique key by appending a number after `prefix` such that it doesn't conflict with any IDs in `g`\n// If `ignored` is not nil, a conflict with the ignored object is allowed. An example use case is to generate a unique ID for a child being\n// hoisted out of its container, and you know the container is going to be deleted.\n//\n// If `included` is not nil, the generated key must also not conflict with a key in `included`, on top of not conflicting with any IDs in `g`.\n// This is for when an operation needs to generate multiple unique keys in one go, like deleting a container and giving new IDs to all children\nfunc generateUniqueKey(g *d2graph.Graph, prefix string, ignored *d2graph.Object, included []string) (key string, edge bool, _ error) {\n\tmk, err := d2parser.ParseMapKey(prefix)\n\tif err != nil {\n\t\treturn \"\", false, err\n\t}\n\n\tif len(mk.Edges) > 1 {\n\t\treturn \"\", false, errors.New(\"cannot generate unique key for edge chain\")\n\t}\n\n\tif len(mk.Edges) == 1 {\n\t\tif mk.EdgeIndex == nil || mk.EdgeIndex.Int == nil {\n\t\t\tmk.EdgeIndex = &d2ast.EdgeIndex{\n\t\t\t\tInt: go2.Pointer(0),\n\t\t\t}\n\t\t}\n\n\t\tedgeTrimCommon(mk)\n\t\tobj := g.Root\n\t\tif mk.Key != nil {\n\t\t\tvar ok bool\n\t\t\tobj, ok = g.Root.HasChild(d2graph.Key(mk.Key))\n\t\t\tif !ok {\n\t\t\t\treturn d2format.Format(mk), true, nil\n\t\t\t}\n\t\t}","sourceCodeStart":2641,"sourceCodeEnd":2677,"githubUrl":"https://github.com/d2lang/d2/blob/0d69dca6f532ceaeacd615d35d1eaa41a238ffdb/d2oracle/edit.go#L2641-L2677","documentation":"generateUniqueKey derives a fresh unique key for new elements, but it only supports single edges. If the requested prefix contains a chain of edges (len(mk.Edges) > 1, e.g. 'a -> b -> c'), no unique key can be generated and it returns this error. Called by Create, Rename, move, MoveIDDeltas and DeleteIDDeltas.","triggerScenarios":"Calling d2oracle.Create (or triggering rename/move/delete deltas) with a prefix key containing two or more connected edge segments, such as 'a -> b -> c'.","commonSituations":"Users typing chained connection syntax into a create box; programmatic generation of multi-hop edge chains in one key; templates that emit 'a -> b -> c' shorthand expecting it to expand.","solutions":["Split the chain into individual edges: create 'a -> b' and 'b -> c' as separate Create calls.","Generate intermediate nodes so each key contains at most one edge.","If you only need the chain as notation, let d2 parser expansion handle it at the language level rather than via d2oracle Create with a chained key.","Validate len(mk.Edges) <= 1 after parsing before invoking any d2oracle edit API with an edge prefix."],"exampleFix":"// before\ndelta, err := d2oracle.Create(graph.Id, \"a -> b -> c\") // edge chain: error\n// after\nd1, err := d2oracle.Create(graph.Id, \"a -> b\")\n// apply d1\nd2, err2 := d2oracle.Create(graph.Id, \"b -> c\")","handlingStrategy":"validation","validationCode":"mk, err := d2parser.ParseMapKey(prefix)\nif err != nil { return err }\nif len(mk.Edges) > 1 {\n    return fmt.Errorf(\"split edge chain %q into single-edge keys before calling d2oracle\", prefix)\n}","typeGuard":"func isEdgeChain(key string) bool {\n    mk, err := d2parser.ParseMapKey(key)\n    return err == nil && len(mk.Edges) > 1\n}","tryCatchPattern":"delta, err := d2oracle.Create(graph.Id, prefix)\nif err != nil {\n    if err.Error() == \"cannot generate unique key for edge chain\" {\n        return splitAndCreateEdges(graph.Id, prefix) // create a -> b, then b -> c\n    }\n    return err\n}","preventionTips":["Reject multi-edge prefixes at input time (forms, CLI)","Split 'a -> b -> c' notation into separate single-edge creates","Sanitize user-supplied keys with d2parser before any d2oracle edit","Document single-edge-only constraint in tooling that builds keys"],"tags":["d2oracle","edges","uniqueness","create"],"backgroundTag":"edge-chain-unsupported","analyzedSha":"0d69dca6f532ceaeacd615d35d1eaa41a238ffdb","analyzedAt":"2026-08-31T12:19:21.182Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}