How to change group owner on a large directory containing locked files
The following is an example of how I would workaround the issue by creating a wrapper script for the chgrp command to act upon a target directory having issues with locked files.

Using a bash script, named chgrpld, I'd pass it the same command line arguments (options, operand and directory) I would if executing chgrp on a directory. Note that my naming convention was simply to add the letters ld to it representing locked directory, in that it works on directories that are locked, or not, and or containing files/directories that are locked too. So where I'd normally use, e.g., chgrp -R staff foo, where foo is a directory, I'd use chgrpld -R staff foo instead. The script is also coded to work on a directory that is not locked and or does not contain locked filesystem objects as well, thus allow the use of chgrpld as a regular replacement for chgrp when targeting a directory, if I don't want to check for locked filesystem objects first or have chgrp fail first to then have to use chgrpld anyway.

Note: This script employes a limited amount of error checking, enough to work under limited testing conditions. It is not coded to handle an embedded newline in the name of a filesystem object, which IMO doesn't belong there in the first place! Feel free to modify as needed/wanted to suite your needs, adding additional error checking if/when wanted/needed.

The testing environment was OS X 10.11.5 in a temporary directory, as shown in the listing below, having two directories, each with one file in it and one of the files locked. One directory/file set is in the admin group and the other set is in the staff group and I'm wanting everything to be in the staff group. (See Testing Note at end of answer for additional testing information.)

The Terminal output shows:


A recursive directory listing.
An attempt to use chgrp, showing its error output.
Using chgrpld, executing without error.
Another recursive directory listing, to show the group has changed.




$ ls -lRO
total 0
drwxr-xr-x  3 user  staff  - 102 Aug  9 2:00 bar
drwxr-xr-x  3 user  admin  - 102 Aug  9 2:00 foo

./bar:
total 0
-rw-r--r--  1 user  staff  - 0 Aug  9 2:00 foo

./foo:
total 0
-rw-r--r--  1 user  admin  uchg 0 Aug  9 2:00 bar
$ chgrp -R staff foo
chgrp: foo/bar: Operation not permitted
$ chgrpld -R staff foo
$ ls -lRO
total 0
drwxr-xr-x  3 user  staff  - 102 Aug  9 2:00 bar
drwxr-xr-x  3 user  staff  - 102 Aug  9 2:00 foo

./bar:
total 0
-rw-r--r--  1 user  staff  - 0 Aug  9 2:00 foo

./foo:
total 0
-rw-r--r--  1 user  staff  uchg 0 Aug  9 2:00 bar
$ 




To create the chgrpld bash script and have it available at the command prompt:

In Terminal:


Type, touch chgrpld and press enter.
Type, open chgrpld and press enter.


From the Browser:


Copy and paste, the code below into the opened chgrpld file and save it, then close the file.


Back in Terminal:


Type, chmod +x chgrpld and press enter.
Type, sudo mkdir -p /usr/local/bin and press enter. Note that on a clean install of OS X 10.11.5, the bin directory at /usr/local did not exist even though it's already in the PATH.


Type in password and press enter.

Type sudo cp chgrpld /usr/local/bin and press enter.


Type in password and press enter. (If necessary.)

chgrpld should now be available to use, just like the chgrp command.




Source code for chgrpld:

#!/bin/bash

o="${@:1:$(($#-1))}"
d="${@: -1}"
if [ -d "$d" ]; then
    f=".locked_files_list_$(date "+%s")"
    find "$d" -flags uchg &gt; "$f" 2&gt;/dev/null
    if [ -s "$f" ]; then
        while read -r l; do
            chflags nouchg "$l"
        done&lt;"$f"
            $(chgrp $o "$d")
        while read -r l; do
            chflags uchg "$l"
        done&lt;"$f"
        rm "$f"
    else
        rm "$f"
        $(chgrp $o "$d")
    fi
else
    echo "  \"$d\" is not a directory!..."
    exit 1
fi
exit 0




Synopsis: chgrpld [−fhv][−R[−H | −L | −P]] group directory


See man chgrp in Terminal for description of options and group operand.
directory is the name/pathname of the target directory that is locked, or not, and or containing files/directories that are locked too. Note that unlike chgrp which allows multiple objects, e.g. group file ... to act upon, chgrpld is coded to act upon one target directory, and recursively with the -R option, at a time.
Note: The user invoking chgrpld must belong to the specified group and be the owner of the directory and locked filesystem objects, or be the super-user.
As expected in SIP versions of the OS, this will not work upon SIP protected filesystem objects if SIP is enabled.




The image below shows syntactical highlighting of the code with spaced and indented comments for easier reading to help explain a little about what the code is doing.





Testing Note: Note that even though the testing environment shown above is limited nonetheless I did test it under a more complexed hierarchal directory structure with many more locked nested directories and files, with names containing spaces, and or backslashes within the filename, without issue. Obviously for demonstration purposes, I'm only showing a bare minimum structure for proof of concept. Again, as noted above, "It is not coded to handle an embedded newline in the name of a filesystem object, which IMO doesn't belong there in the first place!".