Go 1.27 drains response bodies: a Sume job poll loop
Go 1.27 drains unread HTTP/1 body bytes on Close. A stdlib loop that polls GET /v1/jobs/:id/status and honors next_poll_after_seconds.

Short answer
Poll GET /v1/jobs/:id/status in a plain net/http loop, always decode or close the body, sleep for next_poll_after_seconds, and stop when terminal is true. The Go 1.27 release notes say an HTTP/1 Response.Body now drains unread content when it is closed, up to a conservative limit, to allow better connection reuse, so a loop that closes every response keeps its connection warm.
That matters for polling because a video job can take minutes and the loop issues hundreds of tiny GETs. Sume's jobs and results guide describes the contract: every submit returns a durable job id, async is the default mode, and the status endpoint tells you when to look again.
What changed in Go 1.27 that touches a poll client
The release notes page also lists server-side items. They are worth knowing if the same binary both polls Sume and receives webhooks, but only the body-drain note changes client behavior.
| Item | What the notes say |
|---|---|
| HTTP/1 Response.Body | drains unread content on Close, up to a conservative limit, for better connection reuse |
| HTTP/2 server priority | server accepts client priority signals per RFC 9218; Server.DisableClientPriority restores round-robin |
| Server.MaxHeaderValueCount | new field that caps how many header values a server accepts |
| testing/synctest.Sleep | new helper combining time.Sleep and synctest.Wait |
The envelope the loop reads
Every status read returns a data object. The loop below needs only four fields from it, and it never builds a URL by hand beyond the job id you were given at submit time.
Sume documents the same loop as pseudocode: poll, call a progress callback, stop on terminal, then fetch the result only when the job completed. A 409 job_not_completed from the result route means you asked too early; read the failure off the job record instead.
| Field | What it tells the loop |
|---|---|
| terminal | true once the job is completed, failed or canceled; stop polling |
| result_ready | true when GET /v1/jobs/:id/result will return the result |
| sume_status | queued, processing, completed, failed or canceled |
| next_poll_after_seconds | how long to wait before the next read; null once terminal |
| status_url, result_url, events_url, cancel_url | absolute URLs the envelope hands back, so you never build paths by hand |
A stdlib poll loop
Save the file as poll.go and run SUME_API_KEY=... go run poll.go job_123. The key goes in Authorization: Bearer, which the docs use in every curl example. The loop reads from the read budget, which on the Free plan is 4,800 requests per minute against 120 writes, so a two-second cadence is nowhere near it.
package main
import ("encoding/json"; "fmt"; "net/http"; "os"; "time")
func main() {
url := "https://api.sume.com/v1/jobs/" + os.Args[1] + "/status"
for {
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("SUME_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil { fmt.Println(err); os.Exit(1) }
var s struct{ Data struct {
Terminal bool `json:"terminal"`
Status string `json:"sume_status"`
Next *float64 `json:"next_poll_after_seconds"`
} }
err = json.NewDecoder(res.Body).Decode(&s)
res.Body.Close()
if err != nil || res.StatusCode != 200 { fmt.Println("status", res.StatusCode, err); os.Exit(1) }
if s.Data.Terminal { fmt.Println(s.Data.Status); return }
wait := 2.0
if s.Data.Next != nil { wait = *s.Data.Next }
time.Sleep(time.Duration(wait * float64(time.Second)))
}
}What Sume does and does not do
Sume gives every submit a job id in the first response, whichever mode you chose, and keeps the job running if your process dies. It does not push progress: the Developer API has no SSE or WebSocket transport, and GET /v1/jobs/:id/events is a pull snapshot.
A client-side timeout does not cancel anything. If your deadline passes, the job still runs and still bills; store the id and resume from status_url, or call POST /v1/jobs/:id/cancel, which only succeeds before generation starts and otherwise answers 409 job_generation_already_started.
- Never resubmit a paid request because the poll loop timed out; retry the submit only with the same
Idempotency-Key. - Use
webhookmode when you would rather be told, and keep this loop as the backup.
Sources
Related posts
More in Developers
- Go webhook handler: verify the Sume sume-v1 signature
A stdlib Go verifier for x-sume-webhook-signature: HMAC-SHA256 over timestamp.raw_body, five-minute window, constant-time compare, empty secret refused.
- Goose 1.52 recipe consent before extensions: a Sume MCP recipe
Goose 1.52 asks for recipe consent before session/new spawns extensions, and caps recipe size. What to put in a recipe that uses Sume's MCP server.
- got maxRetryAfter and 429: rate_limited vs queue_full on Sume
got retries 429 and honors Retry-After up to maxRetryAfter. Sume sends 429 for rate_limited and for queue_full, which need different waits. Here is the split.
- got does not retry POST by default: enable it safely with Sume
got retries GET, PUT and DELETE but not POST. To retry a Sume paid submit, add POST to retry.methods and send one Idempotency-Key reused on every attempt.
Written by Sume