{"record":{"id":"b59aca8a937a3fd0","repo":"hashicorp/nomad","slug":"missing-volume-claim","errorCode":null,"errorMessage":"missing volume claim","messagePattern":"missing volume claim","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nomad/csi_endpoint.go","lineNumber":713,"sourceCode":"\t\treturn structs.ErrPermissionDenied\n\t}\n\n\tdefer metrics.MeasureSince([]string{\"nomad\", \"volume\", \"unpublish\"}, time.Now())\n\n\tallowVolume := acl.NamespaceValidator(acl.NamespaceCapabilityCSIMountVolume)\n\taclObj, err := v.srv.ResolveACL(args)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err := v.authorizeUnpublish(aclObj, args, allowVolume); err != nil {\n\t\treturn err\n\t}\n\n\tif args.VolumeID == \"\" {\n\t\treturn fmt.Errorf(\"missing volume ID\")\n\t}\n\tif args.Claim == nil {\n\t\treturn fmt.Errorf(\"missing volume claim\")\n\t}\n\n\tws := memdb.NewWatchSet()\n\tstate := v.srv.fsm.State()\n\tvol, err := state.CSIVolumeByID(ws, args.Namespace, args.VolumeID)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif vol == nil {\n\t\treturn fmt.Errorf(\"no such volume\")\n\t}\n\n\tclaim := args.Claim\n\n\t// we need to checkpoint when we first get the claim to ensure we've set the\n\t// initial \"past claim\" state, otherwise a client that unpublishes (skipping\n\t// the node unpublish b/c it's done that work) fail to get written if the\n\t// controller unpublish fails.","sourceCodeStart":695,"sourceCodeEnd":731,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/csi_endpoint.go#L695-L731","documentation":"CSIVolume.Unpublish rejects the request because the CSIVolumeUnpublishRequest has a nil Claim. The claim identifies which allocation/node/read-write state is being released, so without it Nomad cannot checkpoint or detach anything. This is request validation that happens after ACL authorization but before any state mutation.","triggerScenarios":"Raised when args.Claim == nil in Unpublish: the caller supplied the VolumeID but omitted the CSIVolumeClaim (Mode/NodeID/AllocationID/ExternalNodeID) — typically a hand-built HTTP JSON body missing the \"claim\" object or a Go struct literal that only sets VolumeID.","commonSituations":"Custom automation that unpublishes volumes via the raw HTTP API constructing incomplete bodies; unmarshalling payload JSON where nested claim fields were misnamed; scripts copied from the deregister (not unpublish) flow.","solutions":["Populate the Claim struct with at least the AllocationID (and NodeID if known) of the claim being released.","If the intent was to drop the whole volume registration, use `nomad volume delete`/deregister instead of unpublish.","Add a pre-send check that both VolumeID and Claim are set."],"exampleFix":"// before\nbody := fmt.Sprintf(`{\"VolumeID\": %q}`, volID)\n// after\nbody := fmt.Sprintf(`{\"VolumeID\": %q, \"Claim\": {\"AllocationID\": %q, \"NodeID\": %q}}`, volID, allocID, nodeID)","handlingStrategy":"validation","validationCode":"if req.Claim == nil || req.Claim.AllocationID == \"\" {\n    return fmt.Errorf(\"Claim (with AllocationID) must be set before calling Unpublish\")\n}","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"missing volume claim\") {\n    // populate the claim and resend\n}","preventionTips":["Always include the claim's AllocationID (and NodeID when known) in unpublish payloads.","Use `nomad volume detach`/client helpers instead of hand-built HTTP bodies.","Distinguish unpublish (release a claim) from delete (remove registration) in tooling."],"tags":["nomad","csi","validation","request","claim"],"backgroundTag":"missing-required-argument","analyzedSha":"482b49bf1aec006f089bcfc7e632d8f6ac303e5e","analyzedAt":"2026-09-04T07:54:14.808Z","contentChangedAt":"2026-09-04T07:54:14.808Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}