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
- Fix the inner commenter implementation to always return the created Comment on nil error.
- Audit the storage layer used by the commenter to confirm the comment row is actually written.
- Add a unit test asserting AddCommentResult.Comment != nil on success for every implementation.
- 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
- Write contract tests asserting non-nil Comment on success for every commenter implementation.
- Return the created entity from the storage layer, not just an ack.
- Audit third-party implementations against the issueops role contract before registering them.
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.