juicedata/juicefs · error · AclException
Invalid ACL: mask is required and cannot be deleted.
Error message
Invalid ACL: mask is required and cannot be deleted.
What it means
calculateMasks validates the effective-mask invariant: whenever the ACL contains maskable entries (named users/groups, group mask) a mask entry is required. This is thrown when the caller's operation would delete the mask in a scope that needs one without supplying a replacement — POSIX ACL semantics forbid a maskless ACL with maskable entries.
Source
Thrown at sdk/java/src/main/java/io/juicefs/utils/AclTransformation.java:231
// union of group class permissions in each scope.
for (AclEntry entry : aclBuilder) {
scopeFound.add(entry.getScope());
if (entry.getType() == GROUP || entry.getName() != null) {
FsAction scopeUnionPerms = unionPerms.get(entry.getScope());
if (scopeUnionPerms == null) {
scopeUnionPerms = FsAction.NONE;
}
unionPerms.put(entry.getScope(), scopeUnionPerms.or(entry.getPermission()));
}
if (entry.getName() != null) {
maskNeeded.add(entry.getScope());
}
}
// Add mask entry if needed in each scope.
for (AclEntryScope scope : scopeFound) {
if (!providedMask.containsKey(scope) && maskNeeded.contains(scope) && maskDirty.contains(scope)) {
// Caller explicitly removed mask entry, but it's required.
throw new AclException("Invalid ACL: mask is required and cannot be deleted.");
} else if (providedMask.containsKey(scope) && (!scopeDirty.contains(scope) || maskDirty.contains(scope))) {
// Caller explicitly provided new mask, or we are preserving the existing
// mask in an unchanged scope.
aclBuilder.add(providedMask.get(scope));
} else if (maskNeeded.contains(scope) || providedMask.containsKey(scope)) {
// Otherwise, if there are maskable entries present, or the ACL
// previously had a mask, then recalculate a mask automatically.
aclBuilder.add(new AclEntry.Builder().setScope(scope).setType(MASK).setPermission(unionPerms.get(scope)).build());
}
}
}
private static void copyDefaultsIfNeeded(List<AclEntry> aclBuilder) {
Collections.sort(aclBuilder, ACL_ENTRY_COMPARATOR);
ScopedAclEntries scopedEntries = new ScopedAclEntries(aclBuilder);
if (!scopedEntries.getDefaultEntries().isEmpty()) {
List<AclEntry> accessEntries = scopedEntries.getAccessEntries();
List<AclEntry> defaultEntries = scopedEntries.getDefaultEntries();View on GitHub (pinned to c9a67b23e8)
Solutions
- Keep or re-add the mask entry (e.g. 'mask::rwx' or the desired effective mask) in the spec.
- If the mask should be recomputed automatically, don't mention mask in the filter/replace spec at all — let calculateMasks generate it.
- Remove the maskable named entries too, if the intent was to drop everything needing a mask.
Example fix
// before filterAclEntriesByAclSpec(path, "mask::"); // deletes required mask // after filterAclEntriesByAclSpec(path, "user:alice:"); // remove a named entry; mask is regenerated // or explicitly replaceAclEntries(path, "user::rwx,user:alice:rwx,group::r-x,mask::rwx,other::r--");
Defensive patterns
Strategy: validation
Validate before calling
if has_maskable_entries(entries) and mask_entry_missing(resulting_entries):
raise ValueError("spec would drop the required mask; add mask:: or remove mask from the spec") Try / catch
try {
filterAclEntriesByAclSpec(path, spec);
} catch (AclException e) {
if (e.getMessage().contains("mask is required")) {
spec = removeMaskFromSpec(spec); // let the lib recompute the mask
filterAclEntriesByAclSpec(path, spec);
} else throw e;
} Prevention
- Never put mask:: in a filter/remove spec; let the library recalculate it.
- When hand-writing replace specs, always include a mask:: entry if named users/groups exist.
- Recompute the mask explicitly (mask::rwx) after adding maskable entries.
When it happens
Trigger: filterAclEntriesByAclSpec/replaceAclEntries with a spec that removes the mask entry while named user/group entries remain; merging in a named entry but the caller-supplied spec explicitly dropped 'mask::'.
Common situations: Filtering an ACL with 'mask::' in the spec to 'clean up'; hand-writing a replace spec omitting mask; tools migrating ACLs that dropped mask entries.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Invalid ACL: the user, group and other entries are required.
- Invalid default ACL: the user, group and other entries are r
- Invalid ACL: ACL has " + accessEntries.size() + " access ent
- Invalid ACL: ACL has " + defaultEntries.size() + " default e
- Invalid start or len parameter
AI-assisted analysis of juicedata/juicefs@c9a67b23e8 (2026-09-06).
Data as JSON: /api/errors/4e135b66549f8edc.
Report an issue: GitHub.