{"record":{"id":"907a7f921b13a413","repo":"gastownhall/beads","slug":"release-q-the-releaser-reported-success-without","errorCode":null,"errorMessage":"release %q: the releaser reported success without an issue","messagePattern":"release %q: the releaser reported success without an issue","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"internal/httpapi/roles.go","lineNumber":281,"sourceCode":"\n// checkedReleaser is the releaser the release handler is handed.\n//\n// It exists for checkedClaimer's reason exactly: handleRelease writes\n// *result.Issue and reads its RowVersion, so a role that reported success\n// without the row would panic on a live server.\ntype checkedReleaser struct{ inner issueops.Releaser }\n\n// Release refuses a result that reports success without the row the response\n// body is built from.\n//\n// The generic 500, for checkedClaimer's reason and one of its own: there is no\n// wire code that fits and there must not be. A 409 would say the row refused\n// the release when the role said it did not, and a 404 would say the issue does\n// not exist when nothing here knows that. It is a broken implementation.\nfunc (c checkedReleaser) Release(ctx context.Context, req issueops.ReleaseRequest) (issueops.ReleaseResult, error) {\n\tresult, err := c.inner.Release(ctx, req)\n\tif err == nil && result.Issue == nil {\n\t\treturn issueops.ReleaseResult{}, fmt.Errorf(\"release %q: the releaser reported success without an issue\", req.IssueID)\n\t}\n\treturn result, err\n}\n","sourceCodeStart":263,"sourceCodeEnd":285,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/httpapi/roles.go#L263-L285","documentation":"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).","triggerScenarios":"Any HTTP release request where the registered inner releaser returns (ReleaseResult{}, nil) — claiming the release succeeded while omitting the resulting issue.","commonSituations":"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.","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."],"exampleFix":"// before\nfunc (s *store) Release(ctx context.Context, req issueops.ReleaseRequest) (issueops.ReleaseResult, error) {\n    return issueops.ReleaseResult{}, s.releaseIssue(ctx, req) // no issue returned\n}\n// after\nfunc (s *store) Release(ctx context.Context, req issueops.ReleaseRequest) (issueops.ReleaseResult, error) {\n    if err := s.releaseIssue(ctx, req); err != nil {\n        return issueops.ReleaseResult{}, err\n    }\n    issue, err := s.getIssue(ctx, req.IssueID)\n    if err != nil {\n        return issueops.ReleaseResult{}, err\n    }\n    return issueops.ReleaseResult{Issue: issue}, nil\n}","handlingStrategy":"type-guard","validationCode":"// contract test for any releaser implementation\nresult, err := impl.Release(ctx, req)\nif err == nil && result.Issue == nil {\n    t.Fatal(\"implementation violates contract: nil Issue with nil error\")\n}","typeGuard":"// narrow the result before use\nfunc validRelease(r issueops.ReleaseResult, err error) bool {\n    return err == nil && r.Issue != nil\n}","tryCatchPattern":"result, err := httpAPI.Release(ctx, req)\nif err != nil {\n    // contract violations from checkedReleaser land here\n    http.Error(w, err.Error(), http.StatusInternalServerError)\n    return\n}","preventionTips":["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."],"tags":["contract","http-api","nil-result","implementation-bug"],"backgroundTag":"nil-result-on-success","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}