{"record":{"id":"4d799237d60f855f","repo":"projectdiscovery/katana","slug":"add-edge-action-cannot-be-nil","errorCode":null,"errorMessage":"add edge: action cannot be nil","messagePattern":"add edge: action cannot be nil","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/engine/headless/graph/graph.go","lineNumber":87,"sourceCode":"\t\t}\n\n\t\terr = g.graph.AddEdge(n.OriginID, n.UniqueID, func(ep *graph.EdgeProperties) {\n\t\t\tep.Weight = n.Depth\n\t\t\tep.Attributes = edgeAttrs\n\t\t})\n\t\tif err != nil {\n\t\t\tif errors.Is(err, graph.ErrEdgeAlreadyExists) {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn errors.Wrapf(err, \"could not add edge to graph: source vertex %s\", n.OriginID)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (g *CrawlGraph) AddEdge(sourceState, targetState string, action *types.Action) error {\n\tif action == nil {\n\t\treturn errors.New(\"add edge: action cannot be nil\")\n\t}\n\tedgeAttrs := map[string]string{\n\t\t\"label\": action.String(),\n\t}\n\terr := g.graph.AddEdge(sourceState, targetState, func(ep *graph.EdgeProperties) {\n\t\tep.Weight = action.Depth\n\t\tep.Attributes = edgeAttrs\n\t})\n\tif err != nil {\n\t\tif errors.Is(err, graph.ErrEdgeAlreadyExists) {\n\t\t\treturn nil\n\t\t}\n\t\treturn errors.Wrap(err, \"could not add edge to graph\")\n\t}\n\treturn nil\n}\n\nfunc (g *CrawlGraph) GetPageState(id string) (*types.PageState, error) {","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/projectdiscovery/katana/blob/e3e742739c3746f085943ce918fb4e2b8daf6fe6/pkg/engine/headless/graph/graph.go#L69-L105","documentation":"AddEdge in the crawl graph refuses to add an edge whose action attribute is nil, because the edge label is derived from action.String(). Passing a nil action is a programming error by the caller that builds the graph, not a runtime crawl condition.","triggerScenarios":"Calling CrawlGraph.AddEdge(sourceState, targetState, nil) (graph.go:87) — typically when constructing graph edges from actions that were not properly initialized, or deserialized action structs that ended up nil.","commonSituations":"Custom code building/inspecting the crawl graph; recovering actions from serialized state where an action pointer is nil; test harnesses stubbing AddEdge with nil actions.","solutions":["Fix the caller to never pass a nil action; skip or default-initialize the action before AddEdge","Guard call sites with if action == nil { continue } when iterating possibly-empty action slices","Return and propagate the error instead of ignoring it when composing graph updates"],"exampleFix":"// before\ngraph.AddEdge(from, to, action) // panics-free but returns error ignored\n// after\nif action == nil {\n    return nil // skip states with no action\n}\nif err := graph.AddEdge(from, to, action); err != nil { return err }","handlingStrategy":"validation","validationCode":"func safeAddEdge(g *graph.CrawlGraph, from, to string, action *types.Action) error {\n    if action == nil { return nil }\n    return g.AddEdge(from, to, action)\n}","typeGuard":"func hasAction(a *types.Action) bool { return a != nil }","tryCatchPattern":"if err := g.AddEdge(from, to, action); err != nil {\n    return fmt.Errorf(\"add edge %s->%s: %w\", from, to, err)\n}","preventionTips":["Never construct edges from uninitialized action pointers","Skip nil actions when iterating optional action slices","Check AddEdge's returned error instead of discarding it"],"tags":["crawl-graph","nil-argument","api-misuse"],"backgroundTag":"nil-argument","analyzedSha":"e3e742739c3746f085943ce918fb4e2b8daf6fe6","analyzedAt":"2026-09-03T14:55:13.248Z","contentChangedAt":"2026-09-03T14:55:13.248Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}