{"record":{"id":"cb77d71f694d550c","repo":"gastownhall/beads","slug":"db-dependencysqlrepository-hascycle-w","errorCode":null,"errorMessage":"db: DependencySQLRepository.HasCycle: %w","messagePattern":"db: DependencySQLRepository\\.HasCycle: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/db/dependency.go","lineNumber":404,"sourceCode":"\n\t// Snapshot only after all derived blocked-state maintenance has completed.\n\t// Never gated on opts.EmitEvent — a structural removal is as real to a\n\t// replaying consumer as one from an explicit dep verb.\n\tif err := issueops.RecordDepEventInTx(ctx, r.runner, issueops.EventDepRemove, issueID, depType, dependsOnID, depMetadata, actor); err != nil {\n\t\treturn domain.DepDeleteResult{}, err\n\t}\n\n\treturn domain.DepDeleteResult{Found: true, Type: dt, DependsOnID: dependsOnID}, nil\n}\n\nfunc (r *dependencySQLRepositoryImpl) HasCycle(ctx context.Context, issueID, dependsOnID string) (bool, error) {\n\tif issueID == \"\" || dependsOnID == \"\" {\n\t\treturn false, errors.New(\"db: DependencySQLRepository.HasCycle: issueID and dependsOnID must not be empty\")\n\t}\n\n\tcycle, err := issueops.WouldCreateSchedulingCycleInTx(ctx, r.runner, issueID, dependsOnID, nil)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"db: DependencySQLRepository.HasCycle: %w\", err)\n\t}\n\treturn cycle, nil\n}\n\nfunc (r *dependencySQLRepositoryImpl) ListByIssueIDs(ctx context.Context, issueIDs []string, opts domain.DepListOpts) (domain.DepBulkResult, error) {\n\tresult := domain.DepBulkResult{\n\t\tOutgoing: make(map[string][]*types.Dependency),\n\t\tIncoming: make(map[string][]*types.Dependency),\n\t}\n\tif len(issueIDs) == 0 {\n\t\treturn result, nil\n\t}\n\n\tidPlaceholders, idArgs := buildInPlaceholders(issueIDs)\n\ttypeWhere, typeArgs := buildTypeFilter(opts.Types)\n\ttable := pickDepTable(opts.UseWispsTable)\n\n\tif opts.Direction == domain.DepDirectionBoth || opts.Direction == domain.DepDirectionOut {","sourceCodeStart":386,"sourceCodeEnd":422,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/db/dependency.go#L386-L422","documentation":"Wraps a failure from WouldCreateSchedulingCycleInTx inside HasCycle, which detects whether adding issueID -> dependsOnID would create a scheduling cycle. This is a query/execution failure of the cycle-detection traversal, not a 'cycle found' result (a found cycle returns (true, nil)).","triggerScenarios":"Calling HasCycle(ctx, issueID, dependsOnID) with valid non-empty IDs, then the recursive cycle-detection query fails: connection error, timeout on deep graphs, context cancellation, or schema/driver error.","commonSituations":"Deep or wide dependency graphs making the traversal slow enough to hit context deadlines; Dolt server under load; passing an empty ID would instead hit a different validation error, so this one means the query itself broke.","solutions":["Increase the context timeout and retry HasCycle; the check is read-only and safe to repeat.","Break very large dependency graphs into smaller scopes or pre-check only the direct neighborhood.","Check DB connectivity/server health if failures are systemic.","Ensure IDs reference existing issues; non-existent rows can break some traversal implementations."],"exampleFix":"// before\ncycle, err := deps.HasCycle(ctx, a, b) // ctx with 1s deadline\n// after\nctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\ndefer cancel()\ncycle, err := deps.HasCycle(ctx, a, b)","handlingStrategy":"retry","validationCode":"if issueID == \"\" || dependsOnID == \"\" { return errors.New(\"both IDs required\") }\nif err := ctx.Err(); err != nil { return err }","typeGuard":null,"tryCatchPattern":"cycle, err := deps.HasCycle(ctx, a, b)\nif err != nil {\n    if isTransientDBError(err) { // retry read-only check\n        cycle, err = deps.HasCycle(ctxWithLongerDeadline, a, b)\n    }\n    if err != nil { return err } // do NOT treat err as cycle-found\n}","preventionTips":["Never interpret a non-nil error from HasCycle as 'cycle exists' — only (true, nil) means a cycle.","Give deep-graph cycle checks a generous context timeout.","Validate IDs are non-empty and reference existing issues before calling.","Keep the dep table schema current with migrations."],"tags":["database","cycle-detection","dependency"],"backgroundTag":"cycle-check-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}