gastownhall/beads · error
release %q: the releaser reported success without an issue
Error message
release %q: the releaser reported success without an issue
What it means
checkedReleaser wraps the Release role and enforces the same contract as checkedCommenter: success must include the released Issue. If an inner implementation returns nil error with a nil Issue, the wrapper converts it to this error, preventing the HTTP layer from answering success with an empty body (which would imply a 409/404 semantics mismatch).
Source
Thrown at internal/httpapi/roles.go:281
// 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.
func (c checkedReleaser) Release(ctx context.Context, req issueops.ReleaseRequest) (issueops.ReleaseResult, error) {
result, err := c.inner.Release(ctx, req)
if err == nil && result.Issue == nil {
return issueops.ReleaseResult{}, fmt.Errorf("release %q: the releaser reported success without an issue", req.IssueID)
}
return result, err
}
View on GitHub (pinned to 71377f2769)
Solutions
- Fix the inner releaser to return the updated Issue on nil error.
- Verify the storage layer actually persists the release and can read the issue back.
- Add a contract test that Release must return a non-nil Issue on success.
- Check test doubles/mocks used in the HTTP handler tests for empty success results.
Example fix
// before
func (s *store) Release(ctx context.Context, req issueops.ReleaseRequest) (issueops.ReleaseResult, error) {
return issueops.ReleaseResult{}, s.releaseIssue(ctx, req) // no issue returned
}
// after
func (s *store) Release(ctx context.Context, req issueops.ReleaseRequest) (issueops.ReleaseResult, error) {
if err := s.releaseIssue(ctx, req); err != nil {
return issueops.ReleaseResult{}, err
}
issue, err := s.getIssue(ctx, req.IssueID)
if err != nil {
return issueops.ReleaseResult{}, err
}
return issueops.ReleaseResult{Issue: issue}, nil
} Defensive patterns
Strategy: type-guard
Validate before calling
// contract test for any releaser implementation
result, err := impl.Release(ctx, req)
if err == nil && result.Issue == nil {
t.Fatal("implementation violates contract: nil Issue with nil error")
} Type guard
// narrow the result before use
func validRelease(r issueops.ReleaseResult, err error) bool {
return err == nil && r.Issue != nil
} Try / catch
result, err := httpAPI.Release(ctx, req)
if err != nil {
// contract violations from checkedReleaser land here
http.Error(w, err.Error(), http.StatusInternalServerError)
return
} Prevention
- Have release implementations read the issue back after mutation and return it.
- Add contract tests covering Release success shape for every backend.
- Avoid zero-valued success returns in mocks and test doubles.
When it happens
Trigger: Any HTTP release request where the registered inner releaser returns (ReleaseResult{}, nil) — claiming the release succeeded while omitting the resulting issue.
Common situations: A custom release backend that updates the assignee but forgets to load/return the issue; a storage driver bug that skips the read-back step after the update; an in-memory test double that returns zero-valued results.
Related errors
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/907a7f921b13a413.
Report an issue: GitHub.