{"record":{"id":"98883b55111559a3","repo":"nektos/act","slug":"cache-d-not-reserved","errorCode":null,"errorMessage":"cache %d: not reserved","messagePattern":"cache (.+?): not reserved","errorType":"http","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"pkg/artifactcache/handler.go","lineNumber":265,"sourceCode":"\n// PATCH /_apis/artifactcache/caches/:id\nfunc (h *Handler) upload(w http.ResponseWriter, r *http.Request, params httprouter.Params) {\n\tid, err := strconv.ParseUint(params.ByName(\"id\"), 10, 64)\n\tif err != nil {\n\t\th.responseJSON(w, r, 400, err)\n\t\treturn\n\t}\n\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)","sourceCodeStart":247,"sourceCodeEnd":283,"githubUrl":"https://github.com/nektos/act/blob/4f411281417e88660bea1c1a1749aa71ae0bd60f/pkg/artifactcache/handler.go#L247-L283","documentation":"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'.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Re-run the whole workflow so actions/cache performs a fresh reserve -> upload -> commit sequence against the current server.","Avoid restarting act (or wiping its cache dir) between a job's main and post steps; keep --artifact-server-port stable across runs.","Ensure only one act artifact-cache server owns the port/DB being targeted.","If calling the API manually, always POST /caches to reserve first and use the returned ID."],"exampleFix":"# before\n# manual API use skipping reserve:\ncurl -X PATCH .../caches/42 -H 'Content-Range: bytes 0-99' --data-binary @a.tar\n# -> 400 cache 42: not reserved\n\n# after\ncurl -X POST .../caches -d '{\"key\":\"linux-a\",\"version\":\"1\"}'   # returns {\"cacheID\":43,...}\ncurl -X PATCH .../caches/43 -H 'Content-Range: bytes 0-99' --data-binary @a.tar","handlingStrategy":"validation","validationCode":"// Reserve before upload when driving the cache API\ncurl -sX POST \"$URL/api/v1/caches\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -d '{\"key\":\"linux-a\",\"version\":\"1\",\"cacheSize\":1024}'\n# use the returned cacheID for PATCH; never reuse IDs from previous server runs","typeGuard":null,"tryCatchPattern":"resp, err := client.Patch(cacheURL+\"/caches/\"+id, body)\nif err == nil && resp.StatusCode == 400 {\n    if strings.Contains(readBody(resp), \"not reserved\") {\n        // reservation lost (server restarted): start a new reserve->upload->commit cycle\n        id = reserveFresh(key, version)\n        resp, err = client.Patch(cacheURL+\"/caches/\"+id, body)\n    }\n}","preventionTips":["Always reserve (POST /caches) and use the returned ID in the same server session.","Keep act's artifact-server port and cache directory stable for the whole workflow.","Do not restart act between a job's main and post steps when using actions/cache."],"tags":["artifact-cache","http-api","actions-cache","state"],"backgroundTag":null,"analyzedSha":"4f411281417e88660bea1c1a1749aa71ae0bd60f","analyzedAt":"2026-08-15T09:19:46.307Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}