gastownhall/beads · error

%w: comment text cannot be empty

Error message

%w: comment text cannot be empty

What it means

ValidateAddCommentRequest rejects comment text that is empty or whitespace-only, judged on a TRIMMED copy (strings.TrimSpace(request.Text) == ""). A whitespace-only comment carries no information and is almost always a shell quoting accident, so it is refused with wrapped storage.ErrValidation. Note the original Text is left unmodified — text that merely begins with a newline is valid.

Source

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

	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
// ChangedTables drops, so the caller's transaction commits nothing and records
// no history entry: the wisp tables are dolt-ignored and there is nothing to
// version.

View on GitHub (pinned to 71377f2769)

Solutions

  1. Provide non-whitespace comment text in AddCommentRequest.Text.
  2. In scripts, check [ -n "$(printf '%s' "$text" | tr -d '[:space:]')" ] before invoking, or set -u to catch unset variables.
  3. Classify with errors.Is(err, storage.ErrValidation) and treat it as a caller bug, not a transient failure.

Example fix

// before
exec.Command("bd", "comment", id, body) // body may be "   "

// after
if strings.TrimSpace(body) == "" {
	return fmt.Errorf("refusing to add blank comment to %s", id)
}
exec.Command("bd", "comment", id, body)
Defensive patterns

Strategy: validation

Validate before calling

if strings.TrimSpace(text) == "" {
	return errors.New("comment text is blank; refusing to comment")
}

Try / catch

if err := store.AddComment(ctx, req); err != nil {
	if errors.Is(err, storage.ErrValidation) && strings.TrimSpace(req.Text) == "" {
		return fmt.Errorf("blank comment (check shell quoting): %w", err)
	}
	return err
}

Prevention

When it happens

Trigger: Calling AddComment with Text: "" or Text: " "; shell invocations where quoting collapsed (bd comment bd-1 "$EMPTY") producing only spaces; editor integrations saving a comment body that is only blank lines.

Common situations: Shell scripts where a variable interpolates to nothing due to a failed subcommand or unset var; users hitting enter on an empty editor prompt; automated pipelines forwarding a log field that turned out to be whitespace.

Related errors


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