gastownhall/beads · error

db: CommentSQLRepository.InsertRecord: issueID must not be e

Error message

db: CommentSQLRepository.InsertRecord: issueID must not be empty

What it means

After dereferencing the comment, InsertRecord requires IssueID to be non-empty because every comment row is keyed to an issue (issues/issues_wisps table). An empty IssueID would never match the foreign issue, so the repository rejects it up front with this guard.

Source

Thrown at internal/storage/domain/db/comment.go:134

func (r *commentSQLRepositoryImpl) Insert(ctx context.Context, issueID, author, text string, opts domain.CommentOpts) (*types.Comment, error) {
	// Live add: advance past the issue's newest comment so a burst inside one
	// second still reads back in write order (issueops.NextLiveCommentTime).
	// InsertRecord honors a supplied CreatedAt verbatim, which is what keeps
	// imported comments on their original timestamps.
	stamp, err := issueops.NextLiveCommentTime(ctx, r.runner, pickCommentTable(opts.UseWispsTable), issueID, time.Now())
	if err != nil {
		return nil, fmt.Errorf("db: CommentSQLRepository.Insert: %w", err)
	}
	return r.InsertRecord(ctx, &types.Comment{IssueID: issueID, Author: author, Text: text, CreatedAt: stamp}, opts)
}

func (r *commentSQLRepositoryImpl) InsertRecord(ctx context.Context, comment *types.Comment, opts domain.CommentOpts) (*types.Comment, error) {
	if comment == nil {
		return nil, fmt.Errorf("db: CommentSQLRepository.InsertRecord: comment must not be nil")
	}
	copy := *comment
	if copy.IssueID == "" {
		return nil, fmt.Errorf("db: CommentSQLRepository.InsertRecord: issueID must not be empty")
	}

	issueTable := pickIssueTable(opts.UseWispsTable)
	var exists bool
	//nolint:gosec // G201: issueTable is one of two hardcoded constants
	if err := r.runner.QueryRowContext(ctx,
		fmt.Sprintf("SELECT EXISTS(SELECT 1 FROM %s WHERE id = ?)", issueTable), copy.IssueID).Scan(&exists); err != nil {
		return nil, fmt.Errorf("db: CommentSQLRepository.InsertRecord: check issue existence: %w", err)
	}
	if !exists {
		return nil, fmt.Errorf("db: CommentSQLRepository.InsertRecord: issue %s not found", copy.IssueID)
	}

	if copy.CreatedAt.IsZero() {
		copy.CreatedAt = time.Now().UTC()
	} else {
		copy.CreatedAt = copy.CreatedAt.UTC()
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Set comment.IssueID to a valid issue ID before calling InsertRecord.
  2. Resolve the issue (e.g. via a lookup by title/prefix) and use its ID.
  3. Skip insertion when the parent ID is empty and log/report the orphan comment instead of calling the repository.

Example fix

// before
repo.InsertRecord(ctx, &types.Comment{Author: "me", Text: "hi"}, opts)
// after
repo.InsertRecord(ctx, &types.Comment{IssueID: issue.ID, Author: "me", Text: "hi"}, opts)
Defensive patterns

Strategy: validation

Validate before calling

func validateComment(c *types.Comment) error {
    if c == nil || c.IssueID == "" {
        return fmt.Errorf("comment requires a non-empty IssueID")
    }
    return nil
}

Type guard

func hasIssueID(c *types.Comment) bool { return c != nil && c.IssueID != "" }

Try / catch

if _, err := repo.InsertRecord(ctx, c, opts); err != nil {
    if strings.Contains(err.Error(), "issueID must not be empty") {
        // resolve the parent issue and populate IssueID, then retry
    }
}

Prevention

When it happens

Trigger: Calling InsertRecord with a types.Comment whose IssueID field was never set, calling it with comment.IssueID == "" after a failed lookup, or constructing the struct with named fields and omitting IssueID.

Common situations: Import scripts that forgot to map the parent issue key, code paths where the issue ID comes from a config/env value that is empty, or tests constructing partial fixtures.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/55ffa956b056eccf. Report an issue: GitHub.