gastownhall/beads · error
db: Exists: id must not be empty
Error message
db: Exists: id must not be empty
What it means
Exists() validates its issue id before querying the issues table. Because an empty id can never match a row, the repository refuses the query instead of silently returning false, so a caller bug (uninitialized or lost id) surfaces as an explicit error rather than a misleading result.
Source
Thrown at internal/storage/domain/db/issue.go:605
defer rows.Close()
var out []*types.Issue
for rows.Next() {
issue, err := scanIssue(rows)
if err != nil {
return nil, fmt.Errorf("db: GetByIDs: scan: %w", err)
}
out = append(out, issue)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("db: GetByIDs: rows: %w", err)
}
return out, nil
}
func (r *issueSQLRepositoryImpl) Exists(ctx context.Context, id string, opts domain.IssueTableOpts) (bool, error) {
if id == "" {
return false, errors.New("db: Exists: id must not be empty")
}
table := pickIssueTable(opts.UseWispsTable)
//nolint:gosec // G201: table is one of two hardcoded constants
row := r.runner.QueryRowContext(ctx, fmt.Sprintf("SELECT 1 FROM %s WHERE id = ? LIMIT 1", table), id)
var one int
err := row.Scan(&one)
if errors.Is(err, sql.ErrNoRows) {
return false, nil
}
if err != nil {
return false, fmt.Errorf("db: Exists %s: %w", id, err)
}
return true, nil
}
func (r *issueSQLRepositoryImpl) CountForPrefix(ctx context.Context, prefix string, opts domain.IssueTableOpts) (int, error) {
if prefix == "" {
return 0, errors.New("db: CountForPrefix: prefix must not be empty")View on GitHub (pinned to 71377f2769)
Solutions
- Ensure the caller populates the id (e.g. from an existing issue) before calling Exists
- Guard with `if id == "" { return false, nil }` when an empty id legitimately means 'does not exist' in your domain
- Verify the ID was not truncated by string splitting (e.g. `strings.TrimPrefix(id, "bd-")` on an already-bare id)
Example fix
// before
exists, err := repo.Exists(ctx, issue.ID, opts)
// after
if issue.ID == "" {
return fmt.Errorf("issue has no id; cannot check existence")
}
exists, err := repo.Exists(ctx, issue.ID, opts) Defensive patterns
Strategy: validation
Validate before calling
func requireID(id string) error {
if id == "" {
return errors.New("issue id is empty")
}
return nil
} Type guard
func hasID(id string) bool { return strings.TrimSpace(id) != "" } Try / catch
exists, err := repo.Exists(ctx, id, opts)
if err != nil {
return fmt.Errorf("check existence for %q: %w", id, err)
} Prevention
- Never pass struct fields straight into Exists without checking they are populated
- When parsing IDs with TrimPrefix/Split, assert the remainder is non-empty
- Make ID generation mandatory at issue-construction time
When it happens
Trigger: Calling issueSQLRepositoryImpl.Exists(ctx, "", opts) — i.e. any code path that passes an empty/zero-value id, typically from an issue struct whose ID field was never set or from parsing an empty string.
Common situations: Constructing an Issue literal without an ID and calling Exists on it; splitting/parsing an ID string like 'bd-' that yields an empty suffix; passing an empty variable after a failed lookup.
Related errors
- db: CountForPrefix: prefix must not be empty
- db: NextCounterID: prefix must not be empty
- no store is open for this workspace
- not found
- no absolute native user directory is available
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/152fc8b0ecb130b5.
Report an issue: GitHub.