{"record":{"id":"35f9190cce018d08","repo":"hashicorp/nomad","slug":"missing-targetpath-35f919","errorCode":null,"errorMessage":"missing targetPath","messagePattern":"missing targetPath","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"plugins/csi/client.go","lineNumber":899,"sourceCode":"\t\t\t\treq.ExternalID, err)\n\t\tcase codes.Internal:\n\t\t\terr = fmt.Errorf(\"node plugin returned an internal error, check the plugin allocation logs for more information: %v\", err)\n\t\t}\n\t}\n\treturn err\n}\n\nfunc (c *client) NodeUnpublishVolume(ctx context.Context, volumeID, targetPath string, opts ...grpc.CallOption) error {\n\tif err := c.ensureConnected(ctx); err != nil {\n\t\treturn err\n\t}\n\t// These errors should not be returned during production use but exist as aids\n\t// during Nomad development\n\tif volumeID == \"\" {\n\t\treturn fmt.Errorf(\"missing volumeID\")\n\t}\n\tif targetPath == \"\" {\n\t\treturn fmt.Errorf(\"missing targetPath\")\n\t}\n\n\treq := &csipbv1.NodeUnpublishVolumeRequest{\n\t\tVolumeId:   volumeID,\n\t\tTargetPath: targetPath,\n\t}\n\n\t// NodeUnpublishVolume's response contains no extra data. If err == nil, we were\n\t// successful.\n\t_, err := c.nodeClient.NodeUnpublishVolume(ctx, req, opts...)\n\tif err != nil {\n\t\tcode := status.Code(err)\n\t\tswitch code {\n\t\tcase codes.NotFound:\n\t\t\terr = fmt.Errorf(\"%w: volume %q could not be found: %v\",\n\t\t\t\tstructs.ErrCSIClientRPCIgnorable, volumeID, err)\n\t\tcase codes.Internal:\n\t\t\terr = fmt.Errorf(\"node plugin returned an internal error, check the plugin allocation logs for more information: %v\", err)","sourceCodeStart":881,"sourceCodeEnd":917,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/plugins/csi/client.go#L881-L917","documentation":"NodeUnpublishVolume rejects an empty targetPath argument before any gRPC call. Like the missing-volumeID check, this is a development aid to catch internal callers passing incomplete unpublish requests.","triggerScenarios":"Calling NodeUnpublishVolume(ctx, volumeID, \"\") — the targetPath string is empty.","commonSituations":"Task allocation lost its mount directory record; host path interpolation produced an empty string; a plugin/task runner bug cleared the target path before cleanup; tests calling with placeholder values.","solutions":["Fix the caller to pass the allocation's CSI_TARGET_PATH (the mount path used during publish).","Reconstruct the target path from the alloc dir layout if the record was lost (allocDir/alloc_mounts/...).","In tests, provide a valid non-empty target path fixture."],"exampleFix":"// before\nc.NodeUnpublishVolume(ctx, volumeID, targetPath) // targetPath == \"\"\n// after\ntargetPath := filepath.Join(allocDir, alloc.MountDir, \"csi\", volumeID)\nif targetPath == \"\" { return fmt.Errorf(\"target path unresolved\") }\nreturn c.NodeUnpublishVolume(ctx, volumeID, targetPath)","handlingStrategy":"validation","validationCode":"if targetPath == \"\" {\n\treturn fmt.Errorf(\"cannot unpublish: target path is empty\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Derive targetPath from the alloc dir layout deterministically before cleanup.","Never clear mount-path state before unpublish completes.","Test teardown after simulated alloc-state loss."],"tags":["csi","argument-validation","development-guard"],"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"}