{"record":{"id":"f28e2143c04eb804","repo":"nektos/act","slug":"cache-v-q-already-complete","errorCode":null,"errorMessage":"cache %v %q: already complete","messagePattern":"cache (.+?) %q: already complete","errorType":"http","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"pkg/artifactcache/handler.go","lineNumber":273,"sourceCode":"\n\tcache := &Cache{}\n\tdb, err := h.openDB()\n\tif err != nil {\n\t\th.responseJSON(w, r, 500, err)\n\t\treturn\n\t}\n\tdefer db.Close()\n\tif err := db.Get(id, cache); err != nil {\n\t\tif errors.Is(err, bolthold.ErrNotFound) {\n\t\t\th.responseJSON(w, r, 400, fmt.Errorf(\"cache %d: not reserved\", id))\n\t\t\treturn\n\t\t}\n\t\th.responseJSON(w, r, 500, err)\n\t\treturn\n\t}\n\n\tif cache.Complete {\n\t\th.responseJSON(w, r, 400, fmt.Errorf(\"cache %v %q: already complete\", cache.ID, cache.Key))\n\t\treturn\n\t}\n\tdb.Close()\n\tstart, _, err := parseContentRange(r.Header.Get(\"Content-Range\"))\n\tif err != nil {\n\t\th.responseJSON(w, r, 400, err)\n\t\treturn\n\t}\n\tif err := h.storage.Write(cache.ID, start, r.Body); err != nil {\n\t\th.responseJSON(w, r, 500, err)\n\t}\n\th.useCache(id)\n\th.responseJSON(w, r, 200)\n}\n\n// POST /_apis/artifactcache/caches/:id\nfunc (h *Handler) commit(w http.ResponseWriter, r *http.Request, params httprouter.Params) {\n\tid, err := strconv.ParseInt(params.ByName(\"id\"), 10, 64)","sourceCodeStart":255,"sourceCodeEnd":291,"githubUrl":"https://github.com/nektos/act/blob/4f411281417e88660bea1c1a1749aa71ae0bd60f/pkg/artifactcache/handler.go#L255-L291","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before\ncurl -X POST  .../caches/43            # commit\ncurl -X PATCH .../caches/43 ...        # late chunk\n# -> 400 cache 43 \"linux-a\": already complete\n\n# after\ncurl -X PATCH .../caches/43 ...        # all chunks first\ncurl -X POST  .../caches/43            # commit last","handlingStrategy":"validation","validationCode":"// Client-side ordering guard before uploading a chunk\nfunc uploadChunk(id string, chunk []byte, committed bool) error {\n    if committed {\n        return fmt.Errorf(\"cache %d already committed; reserve a new cache to store more data\", id)\n    }\n    return doPatch(id, chunk)\n}","typeGuard":null,"tryCatchPattern":"resp := uploadChunkViaPatch(id, chunk)\nif resp.StatusCode == 400 && strings.Contains(resp.Body, \"already complete\") {\n    // duplicate/late chunk after commit: treat as no-op success, or reserve a new cache\n    return nil\n}","preventionTips":["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."],"tags":["artifact-cache","http-api","actions-cache","state"],"backgroundTag":null,"analyzedSha":"4f411281417e88660bea1c1a1749aa71ae0bd60f","analyzedAt":"2026-08-15T09:19:46.307Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}