nektos/act · warning
cache %v %q: already complete
Error message
cache %v %q: already complete
What it means
After fetching the Cache record for an upload PATCH, the handler rejects the request with HTTP 400 'cache %v %q: already complete' when cache.Complete is true. A cache entry is marked complete by the commit endpoint (POST /caches/:id) after all chunks are uploaded; the upload endpoint refuses to write more data into a finalized cache to prevent corruption.
Source
Thrown at pkg/artifactcache/handler.go:273
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)
}
h.useCache(id)
h.responseJSON(w, r, 200)
}
// POST /_apis/artifactcache/caches/:id
func (h *Handler) commit(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
id, err := strconv.ParseInt(params.ByName("id"), 10, 64)View on GitHub (pinned to 4f41128141)
Solutions
- Ensure upload order: reserve -> all PATCH uploads -> single commit; never PATCH after POST commit.
- If extra data must be stored, reserve a new cache (new key or version suffix) and upload to that ID.
- Make client retries idempotent: check the cache state (or treat 400 'already complete' as success for duplicate final chunks).
- For parallel writers, serialize uploads per cache ID or give each writer its own key.
Example fix
# before curl -X POST .../caches/43 # commit curl -X PATCH .../caches/43 ... # late chunk # -> 400 cache 43 "linux-a": already complete # after curl -X PATCH .../caches/43 ... # all chunks first curl -X POST .../caches/43 # commit last
Defensive patterns
Strategy: validation
Validate before calling
// Client-side ordering guard before uploading a chunk
func uploadChunk(id string, chunk []byte, committed bool) error {
if committed {
return fmt.Errorf("cache %d already committed; reserve a new cache to store more data", id)
}
return doPatch(id, chunk)
} Try / catch
resp := uploadChunkViaPatch(id, chunk)
if resp.StatusCode == 400 && strings.Contains(resp.Body, "already complete") {
// duplicate/late chunk after commit: treat as no-op success, or reserve a new cache
return nil
} Prevention
- Send all PATCH uploads before the single commit POST.
- Make retry logic aware of commit state to avoid post-commit uploads.
- Give parallel writers separate cache keys instead of sharing one ID.
When it happens
Trigger: Sending additional PATCH /caches/:id uploads after the commit (POST) for that ID already succeeded: duplicated/retried upload requests, a client bug re-uploading after commit, or scripts that commit early and then continue chunk uploads.
Common situations: actions/cache retry logic firing after a slow commit; scripts driving the cache API manually with wrong ordering; parallel jobs sharing one cache ID and committing while another still uploads; network retries duplicating queued chunks after finalization.
Related errors
- cache %d: not reserved
- unable to determine outbound IP address
- generate auth token: %w
- find cache: %w
- insert cache: %w
AI-assisted analysis of nektos/act@4f41128141 (2026-08-15).
Data as JSON: /api/errors/f28e2143c04eb804.
Report an issue: GitHub.