gastownhall/beads · error

: add comment requires an issue ID

Error message

%w: add comment requires an issue ID

What it means

ValidateAddCommentRequest rejects an AddCommentRequest with an empty IssueID. A comment must anchor to an existing issue or wisp row; with no ID there is nothing to attach to, so the request fails validation with wrapped storage.ErrValidation before any query runs.

Solutions

  1. Populate request.IssueID with the target issue's ID before calling AddComment.
  2. Validate the ID is non-empty (and well-formed, e.g. has a prefix) at the call site and report which input was missing.
  3. Classify with errors.Is(err, storage.ErrValidation) and reject the request upstream instead of retrying.

Example fix

// before
req := publicops.AddCommentRequest{Author: author, Text: text}
_ = store.AddComment(ctx, req) // fails: no issue ID

// after
if issueID == "" {
	return fmt.Errorf("usage: bd comment <issue-id> <text>")
}
req := publicops.AddCommentRequest{IssueID: issueID, Author: author, Text: text}
if err := store.AddComment(ctx, req); err != nil { return err }
Defensive patterns

Strategy: validation

Validate before calling

if req.IssueID == "" {
	return errors.New("issue ID is required to add a comment")
}

Try / catch

if err := store.AddComment(ctx, req); err != nil {
	if errors.Is(err, storage.ErrValidation) {
		return fmt.Errorf("invalid request (issue ID?): %w", err)
	}
	return err
}

Prevention

When it happens

Trigger: Calling AddComment with publicops.AddCommentRequest{Author: "alice", Text: "note"} and IssueID unset; computing the issue ID from a variable that failed to populate (empty CLI arg, failed lookup); marshalling a request from user input where the issue field was never filled.

Common situations: CLI tools that derive the issue ID from the current branch name or a positional arg and get an empty string; automation that loops over a list where one entry lacks an ID; API consumers omitting the issue_id key in a JSON body.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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

Appendix: source

Thrown at internal/storage/issueops/commenter.go:25

	"strings"

	"github.com/steveyegge/beads/internal/storage"
	publicops "github.com/steveyegge/beads/issueops"
)

// ValidateAddCommentRequest applies the request rules every Commenter
// implementation shares.
//
// Blankness is decided on a TRIMMED copy and the request's own Text is left
// alone: a comment of nothing but whitespace carries no information and is
// almost always a shell quoting accident, but a comment that merely begins
// with a newline is a comment.
func ValidateAddCommentRequest(request publicops.AddCommentRequest) error {
	if request.Author == "" {
		return fmt.Errorf("%w: add comment requires an author", storage.ErrValidation)
	}
	if request.IssueID == "" {
		return fmt.Errorf("%w: add comment requires an issue ID", storage.ErrValidation)
	}
	if strings.TrimSpace(request.Text) == "" {
		return fmt.Errorf("%w: comment text cannot be empty", storage.ErrValidation)
	}
	return nil
}

// AddCommentCommitMessage is the history entry a comment records. It is the
// spelling both stores' own AddIssueComment already wrote.
func AddCommentCommitMessage(issueID string) string {
	return "bd: comment " + issueID
}

// ExecuteAddComment appends one comment in tx and reports the durable tables
// changed. It is the store-backed body behind the Commenter accessor; the
// unit-of-work provider has its own, for the reason Lifecycle does.
//
// A comment on an ephemeral row changes only wisp_comments, which

View on GitHub (pinned to 71377f2769)