gastownhall/beads · error

add comment %q: the commenter reported success without a com

Error message

add comment %q: the commenter reported success without a comment

What it means

checkedCommenter is a wrapper around the AddComment role that enforces a contract: a nil error must always come with a non-nil Comment. When an inner implementation returns success without a comment, the wrapper rejects it as a broken implementation rather than passing a bogus success to HTTP handlers, which would otherwise produce an inconsistent 200 with no comment payload.

Source

Thrown at internal/httpapi/roles.go:259

// checkedCommenter is the commenter the add-comment handler is handed.
//
// It exists for checkedClaimer's reason exactly: handleAddComment writes
// *result.Comment onto the wire, so a role that reported success without the row
// would panic on a live server.
type checkedCommenter struct{ inner issueops.Commenter }

// AddComment refuses a result that reports success without the row the response
// body is built from.
//
// The generic 500, for checkedClaimer's reason. There is no wire code that fits
// and there must not be: a 404 would say the issue does not exist when the role
// just said it appended a comment to it, and this operation has no conflict code
// at all. It is a broken implementation.
func (c checkedCommenter) AddComment(ctx context.Context, req issueops.AddCommentRequest) (issueops.AddCommentResult, error) {
	result, err := c.inner.AddComment(ctx, req)
	if err == nil && result.Comment == nil {
		return issueops.AddCommentResult{}, fmt.Errorf("add comment %q: the commenter reported success without a comment", req.IssueID)
	}
	return result, err
}

// checkedReleaser is the releaser the release handler is handed.
//
// It exists for checkedClaimer's reason exactly: handleRelease writes
// *result.Issue and reads its RowVersion, so a role that reported success
// without the row would panic on a live server.
type checkedReleaser struct{ inner issueops.Releaser }

// Release refuses a result that reports success without the row the response
// body is built from.
//
// The generic 500, for checkedClaimer's reason and one of its own: there is no
// wire code that fits and there must not be. A 409 would say the row refused
// the release when the role said it did not, and a 404 would say the issue does
// not exist when nothing here knows that. It is a broken implementation.

View on GitHub (pinned to 71377f2769)

Solutions

  1. Fix the inner commenter implementation to always return the created Comment on nil error.
  2. Audit the storage layer used by the commenter to confirm the comment row is actually written.
  3. Add a unit test asserting AddCommentResult.Comment != nil on success for every implementation.
  4. If using a third-party implementation, check its issue tracker/upstream for contract-violating versions.

Example fix

// before: broken inner implementation
func (s *store) AddComment(ctx context.Context, req issueops.AddCommentRequest) (issueops.AddCommentResult, error) {
    if err := s.insertComment(ctx, req); err != nil {
        return issueops.AddCommentResult{}, err
    }
    return issueops.AddCommentResult{}, nil // contract violation
}
// after: return the created comment
func (s *store) AddComment(ctx context.Context, req issueops.AddCommentRequest) (issueops.AddCommentResult, error) {
    c, err := s.insertComment(ctx, req)
    if err != nil {
        return issueops.AddCommentResult{}, err
    }
    return issueops.AddCommentResult{Comment: c}, nil
}
Defensive patterns

Strategy: type-guard

Validate before calling

// contract test to run against any commenter implementation
result, err := impl.AddComment(ctx, req)
if err == nil && result.Comment == nil {
    t.Fatal("implementation violates contract: nil Comment with nil error")
}

Type guard

// narrow the result before use
func validComment(r issueops.AddCommentResult, err error) bool {
    return err == nil && r.Comment != nil
}

Try / catch

result, err := httpAPI.AddComment(ctx, req)
if err != nil {
    // includes contract violations surfaced by checkedCommenter
    http.Error(w, err.Error(), http.StatusInternalServerError)
    return
}

Prevention

When it happens

Trigger: Any HTTP add-comment request where the registered inner commenter returns (AddCommentResult{}, nil) — i.e., the backing store claims append success but did not return the created comment.

Common situations: A custom or third-party commenter implementation violating the issueops contract; a partially-migrated backend that returns an empty result on success; a bug in a new storage driver that drops the created comment from the result.

Related errors


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