{"record":{"id":"4999ae034ec428c7","repo":"gastownhall/beads","slug":"count-by-id-sourceid-must-not-be-empty","errorCode":null,"errorMessage":"count by id: sourceID must not be empty","messagePattern":"count by id: sourceID must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/dependency.go","lineNumber":549,"sourceCode":"\nfunc (u *dependencyUseCaseImpl) iterWithMetadata(ctx context.Context, sourceID string, filter DepListFilter, useWisp bool) (storage.Iter[types.IssueWithDependencyMetadata], error) {\n\tif sourceID == \"\" {\n\t\treturn nil, fmt.Errorf(\"iter dep metadata: sourceID must not be empty\")\n\t}\n\tit, err := u.depRepo.IterWithIssueMetadata(ctx, sourceID, DepListOpts{\n\t\tTypes:         filter.Types,\n\t\tDirection:     filter.Direction,\n\t\tUseWispsTable: useWisp,\n\t})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"iter dep metadata: %w\", err)\n\t}\n\treturn it, nil\n}\n\nfunc (u *dependencyUseCaseImpl) countByID(ctx context.Context, sourceID string, filter DepListFilter, useWisp bool) (int64, error) {\n\tif sourceID == \"\" {\n\t\treturn 0, fmt.Errorf(\"count by id: sourceID must not be empty\")\n\t}\n\tn, err := u.depRepo.CountByID(ctx, sourceID, DepListOpts{\n\t\tTypes:         filter.Types,\n\t\tDirection:     filter.Direction,\n\t\tUseWispsTable: useWisp,\n\t})\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"count by id: %w\", err)\n\t}\n\treturn n, nil\n}\n\nfunc (u *dependencyUseCaseImpl) list(ctx context.Context, ids []string, filter DepListFilter, useWisp bool) (DepBulkResult, error) {\n\tif len(ids) == 0 {\n\t\treturn DepBulkResult{\n\t\t\tOutgoing: map[string][]*types.Dependency{},\n\t\t\tIncoming: map[string][]*types.Dependency{},\n\t\t}, nil","sourceCodeStart":531,"sourceCodeEnd":567,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/dependency.go#L531-L567","documentation":"Validation guard in countByID: the source ID for counting dependencies is empty. CountByIssueID/CountByWispID cannot meaningfully count for a blank ID, so the use-case rejects it up front.","triggerScenarios":"Calling CountByIssueID or CountByWispID with sourceID == \"\".","commonSituations":"Caller passed an ID from an unsaved or deleted issue; a variable initialized to \"\" used as a placeholder; upstream filtering produced an empty ID string.","solutions":["Fetch a persisted issue ID before counting","Add an empty-string check before the count call","Confirm the wisp/issue was created (Create returns the ID you should reuse)"],"exampleFix":"// before\nn, err := u.CountByIssueID(ctx, issueID, filter) // issueID == \"\"\n// after\nif issueID == \"\" {\n\treturn 0, fmt.Errorf(\"issueID required\")\n}\nn, err := u.CountByIssueID(ctx, issueID, filter)","handlingStrategy":"validation","validationCode":"if wispID == \"\" {\n\treturn fmt.Errorf(\"cannot count deps: ID is empty\")\n}","typeGuard":"func hasID(id string) bool { return id != \"\" }","tryCatchPattern":"n, err := u.CountByWispID(ctx, wispID, filter)\nif err != nil && strings.Contains(err.Error(), \"sourceID must not be empty\") {\n\t// caller bug: not retryable; fix ID sourcing\n}","preventionTips":["Check that the issue/wisp was created before counting its deps","Never use \"\" as a sentinel ID; use an explicit nil/absent concept upstream","Add empty-ID unit tests around count call sites","Do not retry this validation error"],"tags":["validation","dependency","empty-id","count"],"backgroundTag":"empty-required-id","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}