nektos/act · warning

cache %d: not reserved

Error message

cache %d: not reserved

What it means

The cache upload endpoint (PATCH /caches/:id) first loads the Cache record from the bolthold DB by the numeric ID from the URL. IDs are handed out by the reserve endpoint; if db.Get returns bolthold.ErrNotFound, no live reservation exists for that ID — it was never reserved, was for a different server instance (DB wiped), or the ID is simply wrong — and the server responds HTTP 400 'cache %d: not reserved'.

Source

Thrown at pkg/artifactcache/handler.go:265

// PATCH /_apis/artifactcache/caches/:id
func (h *Handler) upload(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
	id, err := strconv.ParseUint(params.ByName("id"), 10, 64)
	if err != nil {
		h.responseJSON(w, r, 400, err)
		return
	}

	cache := &Cache{}
	db, err := h.openDB()
	if err != nil {
		h.responseJSON(w, r, 500, err)
		return
	}
	defer db.Close()
	if err := db.Get(id, cache); err != nil {
		if errors.Is(err, bolthold.ErrNotFound) {
			h.responseJSON(w, r, 400, fmt.Errorf("cache %d: not reserved", id))
			return
		}
		h.responseJSON(w, r, 500, err)
		return
	}

	if cache.Complete {
		h.responseJSON(w, r, 400, fmt.Errorf("cache %v %q: already complete", cache.ID, cache.Key))
		return
	}
	db.Close()
	start, _, err := parseContentRange(r.Header.Get("Content-Range"))
	if err != nil {
		h.responseJSON(w, r, 400, err)
		return
	}
	if err := h.storage.Write(cache.ID, start, r.Body); err != nil {
		h.responseJSON(w, r, 500, err)

View on GitHub (pinned to 4f41128141)

Solutions

  1. Re-run the whole workflow so actions/cache performs a fresh reserve -> upload -> commit sequence against the current server.
  2. Avoid restarting act (or wiping its cache dir) between a job's main and post steps; keep --artifact-server-port stable across runs.
  3. Ensure only one act artifact-cache server owns the port/DB being targeted.
  4. If calling the API manually, always POST /caches to reserve first and use the returned ID.

Example fix

# before
# manual API use skipping reserve:
curl -X PATCH .../caches/42 -H 'Content-Range: bytes 0-99' --data-binary @a.tar
# -> 400 cache 42: not reserved

# after
curl -X POST .../caches -d '{"key":"linux-a","version":"1"}'   # returns {"cacheID":43,...}
curl -X PATCH .../caches/43 -H 'Content-Range: bytes 0-99' --data-binary @a.tar
Defensive patterns

Strategy: validation

Validate before calling

// Reserve before upload when driving the cache API
curl -sX POST "$URL/api/v1/caches" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"key":"linux-a","version":"1","cacheSize":1024}'
# use the returned cacheID for PATCH; never reuse IDs from previous server runs

Try / catch

resp, err := client.Patch(cacheURL+"/caches/"+id, body)
if err == nil && resp.StatusCode == 400 {
    if strings.Contains(readBody(resp), "not reserved") {
        // reservation lost (server restarted): start a new reserve->upload->commit cycle
        id = reserveFresh(key, version)
        resp, err = client.Patch(cacheURL+"/caches/"+id, body)
    }
}

Prevention

When it happens

Trigger: A GitHub Actions cache client PATCHing to a cache ID that was not reserved in this server's database: act restarted between reserve and upload (fresh temp DB loses reservations), multiple act instances behind one address, replayed/stale requests after cache eviction, or hand-crafted API calls skipping the reserve step.

Common situations: actions/cache's post step failing after act was re-run mid-workflow; a long workflow spanning an act restart; CI scripts caching the wrong port after changing --artifact-server-port; parallel jobs interleaving IDs from different servers.

Related errors


AI-assisted analysis of nektos/act@4f41128141 (2026-08-15). Data as JSON: /api/errors/98883b55111559a3. Report an issue: GitHub.