go-release-tag-stale
Root cause. v0.1.0 was tagged 53 commits behind HEAD and its handlers shipped bodies that violated the OpenAPI contract (POST /cancel → {"status":"ok"}; DELETE /sessions/{id} → 204 No Content). Since go get @latest resolves the highest semver tag from the proxy/VCS tags — not HEAD — consumers kept pulling the stale, non-compliant build. The fix is two-layered: (1) make the wire bodies compliant at HEAD, (2) re-release a higher tag at HEAD so @latest resolves the fixed code.
api.go)Before (shipped in v0.1.0):
// BUG: contract says {"cancelled","cancelled_decision_id"}; 404 when unknown
type cancelReply struct{ Status string `json:"status"` }
func (s *server) handleCancel(w http.ResponseWriter, r *http.Request) {
...
writeJSON(w, http.StatusOK, cancelReply{Status: "ok"}) // always "ok"
}
// BUG: contract says 200 {"terminated","session_id"}
func (s *server) handleTerminate(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent) // 204, empty body
}
After (the fix, tagged v0.1.1):
// CancelResponse: {"cancelled": true, "cancelled_decision_id": "..."}
type CancelResponse struct {
Cancelled bool `json:"cancelled"`
CancelledDecisionID string `json:"cancelled_decision_id"`
}
type TerminateResponse struct {
Terminated bool `json:"terminated"`
SessionID string `json:"session_id"`
}
func (s *server) handleCancel(w http.ResponseWriter, r *http.Request) {
// ...decode req...
if !s.knownDecision(req.DecisionID) {
writeJSON(w, http.StatusNotFound, errorResponse{Error: "decision not found"})
return
}
writeJSON(w, http.StatusOK, CancelResponse{Cancelled: true, CancelledDecisionID: req.DecisionID})
}
func (s *server) handleTerminate(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
writeJSON(w, http.StatusOK, TerminateResponse{Terminated: true, SessionID: id}) // 200, idempotent
}
Regression tests (api_test.go) assert: cancel body has cancelled+cancelled_decision_id and no status field, cancel-unknown → 404, delete → 200 with terminated+session_id.
CHANGELOG.md)Rename the mislabeled header ## [1.0.0] → ## [0.1.0] (matches the actual tag), and prepend:
```markdown
Reproduced end-to-end in `/tmp/runner-demo/runner` (module `example.com/runner`, `v0.1.0` tagged at commit 1, 52 unrelated commits, fix commit #54 at HEAD, annotated tag, bare origin, file-based module proxy). Real outputs:
**Staleness (before fix):** `git rev-list --count v0.1.0..HEAD` → **53**. Proxy containing only `v0.1.0`: `go list -m -json @latest` → `"Version": "v0.1.0"` (the broken tag wins).
**Broken `v0.1.0` live probe:** `cancel` → `{"status":"ok"}`; `delete` → HTTP **204**, empty body (confirmed the non-compliance).
**Fixed HEAD / `v0.1.1` live probe:**
```
process: HTTP 200 body: {"session_id":"sess-00000001"}
cancel-unknown: HTTP 404 body: {"error":"decision not found"}
delete: HTTP 200 body: {"terminated":true,"session_id":"sess-00000001"}
```
**Guard 4/4:**
1. **CHANGELOG** — `## [v0.1.1]` header present; `## [1.0.0]` mislabeled header removed; `## [0.1.0]` present. ✓
2. **Tag** — `v0.1.1` annotated (`git cat-file -t` → `tag`), `git tag --points-at HEAD` → `v0.1.1`; pushed to origin (`refs/tags/v0.1.1` + peeled `^{}` on remote). ✓
3. **@latest** — after adding `v0.1.1` to the proxy: `go list -m -f '@latest -> {{.Version}}'` → **`@latest -> v0.1.1`**; module zip validated by `go mod download -json example.com/runner@v0.1.1`. ✓
4. **Wire contract** — process 200 / cancel-unknown 404 / delete 200 `{terminated, session_id}`, all confirmed by live curl. ✓
**Edge cases tested:** cancel success body contains `cancelled`+`cancelled_decision_id` and *not* legacy `status` (`TestCancelWireBody`); cancel-unknown → 404 (not 200-ok); delete idempotent 200 with body (not 204); process 200 with `session_id`; `go vet` clean, `gofmt` clean; all 4 unit tests PASS.
---{"model": "deepseek-v4-flash", "problem_class": "go-release-tag-stale", "result": "passed", "tests": 4}