jenkinsci/jenkins · error · IOException

Invalid mode: {}

Error message

Invalid mode: {}

What it means

Thrown by Util.modeToPermissions(int mode) when the mode value, after masking with 07777 (stripping file-type bits), still contains bits in the 07000 range — i.e., setuid (04000), setgid (02000), or sticky (01000). The check is (mode & 0777) != mode after masking; if any bit above 0777 remains, the mode is rejected because these special permission bits are not supported by the POSIX file permission mapping.

Source

Thrown at core/src/main/java/hudson/Util.java:1779

    public static int permissionsToMode(Set<PosixFilePermission> permissions) {
        PosixFilePermission[] allPermissions = PosixFilePermission.values();
        int result = 0;
        for (PosixFilePermission allPermission : allPermissions) {
            result <<= 1;
            result |= permissions.contains(allPermission) ? 1 : 0;
        }
        return result;
    }

    @Restricted(NoExternalUse.class)
    public static Set<PosixFilePermission> modeToPermissions(int mode) throws IOException {
         // Anything larger is a file type, not a permission.
        int PERMISSIONS_MASK = 07777;
        // setgid/setuid/sticky are not supported.
        int MAX_SUPPORTED_MODE = 0777;
        mode = mode & PERMISSIONS_MASK;
        if ((mode & MAX_SUPPORTED_MODE) != mode) {
            throw new IOException("Invalid mode: " + mode);
        }
        PosixFilePermission[] allPermissions = PosixFilePermission.values();
        Set<PosixFilePermission> result = EnumSet.noneOf(PosixFilePermission.class);
        for (int i = 0; i < allPermissions.length; i++) {
            if ((mode & 1) == 1) {
                result.add(allPermissions[allPermissions.length - i - 1]);
            }
            mode >>= 1;
        }
        return result;
    }

    /**
     * Converts a {@link File} into a {@link Path} and checks runtime exceptions.
     * @throws IOException if {@code f.toPath()} throws {@link InvalidPathException}.
     */
    @Restricted(NoExternalUse.class)
    public static @NonNull Path fileToPath(@NonNull File file) throws IOException {

View on GitHub (pinned to 2e228ff40b)

Solutions

  1. Mask the mode value to 0777 before passing it: use mode & 0777 to strip setuid/setgid/sticky bits.
  2. If special bits are genuinely needed, handle them separately with native filesystem calls (e.g., chmod via ProcessBuilder) since Jenkins' POSIX permission API does not support them.
  3. Update the configuration input to only accept 3-digit octal values (000-777).

Example fix

// before
Set<PosixFilePermission> perms = Util.modeToPermissions(02755);

// after
Set<PosixFilePermission> perms = Util.modeToPermissions(02755 & 0777); // strips setgid
Defensive patterns

Strategy: validation

Validate before calling

// Strip special bits before calling modeToPermissions
int safeMode = mode & 0777; // removes setuid/setgid/sticky
Set<PosixFilePermission> perms = Util.modeToPermissions(safeMode);

Try / catch

try {
    Set<PosixFilePermission> perms = Util.modeToPermissions(mode);
} catch (IOException e) {
    if (e.getMessage().startsWith("Invalid mode:")) {
        // Retry with stripped special bits
        perms = Util.modeToPermissions(mode & 0777);
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: modeToPermissions is called with a mode like 02755 (setgid), 04755 (setuid), 01775 (sticky), or 07777 (all bits) — after masking with PERMISSIONS_MASK (07777), the result still has bits above MAX_SUPPORTED_MODE (0777), so the guard fails.

Common situations: Passing a raw Unix octal mode from a configuration field that allows setgid/setuid/sticky bits; copying a chmod value from system documentation that includes special bits; a plugin or configuration UI that accepts full numeric modes without filtering special bits.

Related errors


AI-assisted analysis of jenkinsci/jenkins@2e228ff40b (2026-08-14). Data as JSON: /api/errors/029df4405e6e9069. Report an issue: GitHub.