{"record":{"id":"cb7662dd8208b2fa","repo":"gastownhall/beads","slug":"final-cycle-check-failed-no-edges-added-w","errorCode":null,"errorMessage":"final cycle check failed (no edges added): %w","messagePattern":"final cycle check failed \\(no edges added\\): %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/dependency_editor.go","lineNumber":227,"sourceCode":"// them off entirely, so this is the check that actually holds the invariant.\n//\n// The message is built through domain.NewCycleError so it errors.Is-matches\n// ErrDependencyCycle while rendering byte-for-byte what the direct bulk path\n// already prints.\nfunc checkAddedEdgesForCycles(ctx context.Context, tx *sql.Tx, edges []publicops.DependencyEdge) error {\n\tvar pairs [][2]string\n\tfor _, edge := range edges {\n\t\tif !types.IsSchedulingEdge(edge.Type) {\n\t\t\tcontinue\n\t\t}\n\t\tpairs = append(pairs, [2]string{edge.IssueID, edge.DependsOnID})\n\t}\n\tif len(pairs) == 0 {\n\t\treturn nil\n\t}\n\tgraph := make(map[string][]string)\n\tif err := AppendSchedulingGraphInTx(ctx, tx, cycleDetectionTables(), graph); err != nil {\n\t\treturn fmt.Errorf(\"final cycle check failed (no edges added): %w\", err)\n\t}\n\tif cyclePath := CycleThroughEdgesInGraph(graph, pairs); cyclePath != \"\" {\n\t\treturn domain.NewCycleError(\"dependency cycle would be created: %s (no edges added; run 'bd dep cycles' for analysis)\", cyclePath)\n\t}\n\treturn nil\n}\n\n// ExecuteRemoveDependency removes one edge in tx and reports the durable\n// tables changed.\n//\n// A missing edge reports Removed false and NO changed tables, which is how the\n// callers spell \"commit nothing\": removing an edge that was never there leaves\n// the graph it already had, and a history entry for it would be a commit with\n// nothing in it.\nfunc ExecuteRemoveDependency(ctx context.Context, tx *sql.Tx, request publicops.RemoveDependencyRequest) (publicops.RemoveDependencyResult, ChangedTables, error) {\n\t// Routing is READ here rather than pinned, unlike the add. A removal\n\t// cannot put an edge anywhere, so pinning it would only mean failing to\n\t// remove an edge the caller named — and the staging has to name the tables","sourceCodeStart":209,"sourceCodeEnd":245,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/dependency_editor.go#L209-L245","documentation":"During ExecuteAddDependencies' end gate, checkAddedEdgesForCycles builds the full scheduling graph inside the transaction via AppendSchedulingGraphInTx; if that graph-load query fails, the whole add is aborted with this wrapped error. No edges have been added at this point, so the transaction is purely failed by an infrastructure/query problem, not by a real cycle. The %w preserves the underlying driver error for errors.As/errors.Is inspection.","triggerScenarios":"Any failure of the tx.QueryContext-backed AppendSchedulingGraphInTx while loading cycle-detection tables (cycleDetectionTables) mid-transaction — e.g. a database connection drop, lock timeout, or driver error during ExecuteAddDependencies or runEndGate.","commonSituations":"Dolt/server connection interrupted mid-transaction; database under heavy lock contention from concurrent writers; transient driver errors during long graph loads on large issue graphs.","solutions":["Inspect the wrapped cause with errors.As to identify the driver error (connection lost, lock timeout, etc.) and address that root cause.","Retry the whole ExecuteAddDependencies operation in a fresh transaction — the failed tx rolled back and left no edges added.","Check database connectivity and server health if the error recurs; verify no competing transaction holds long locks on the dependency tables."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"err := editor.ExecuteAddDependencies(ctx, req)\nif err != nil {\n    var derr error\n    if errors.As(err, &derr) && strings.Contains(err.Error(), \"final cycle check failed\") {\n        // tx rolled back with no edges added; safe to retry whole operation\n        return retryOperation(ctx, req)\n    }\n    return err\n}","preventionTips":["Keep database connections healthy (keepalives, sane timeouts) since the check runs mid-transaction.","Avoid concurrent long transactions on the dependency tables to reduce lock contention.","Retry idempotently: no edges are added on this failure, so a whole-operation retry is safe."],"tags":["go","database","transactions","cycle-detection","transient"],"backgroundTag":"cycle-detection-query-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}