# Buggy code
def workspace_dir(self):
    """
    Return the default location on the filesystem for opening and closing
    files.
    """
    device_dir = None
    # Attempts to find the path on the filesystem that represents the
    # plugged in CIRCUITPY board.
    if os.name == "posix":
        # We're on Linux or OSX
        for mount_command in ["mount", "/sbin/mount"]:
            try:
                mount_output = check_output(mount_command).splitlines()
                mounted_volumes = [x.split()[2] for x in mount_output]
                for volume in mounted_volumes:
                    tail = os.path.split(volume)[-1]
                    if tail.startswith(b"CIRCUITPY") or tail.startswith(b"PYBFLASH"):
                        device_dir = volume.decode("utf-8")
                        break
            except FileNotFoundError:
                next
    elif os.name == "nt":
        # We're on Windows.

        def get_volume_name(disk_name):
            """
            Each disk or external device connected to windows has an
            attribute called "volume name". This function returns the
            volume name for the given disk/device.

            Code from http://stackoverflow.com/a/12056414
            """
            vol_name_buf = ctypes.create_unicode_buffer(1024)
            ctypes.windll.kernel32.GetVolumeInformationW(
                ctypes.c_wchar_p(disk_name),
                vol_name_buf,
                ctypes.sizeof(vol_name_buf),
                None,
                None,
                None,
                None,
                0,
            )
            return vol_name_buf.value

        #
        # In certain circumstances, volumes are allocated to USB
        # storage devices which cause a Windows popup to raise if their
        # volume contains no media. Wrapping the check in SetErrorMode
        # with SEM_FAILCRITICALERRORS (1) prevents this popup.
        #
        old_mode = ctypes.windll.kernel32.SetErrorMode(1)
        try:
            for disk in "ABCDEFGHIJKLMNOPQRSTUVWXYZ":
                path = "{}:\\".format(disk)
                if os.path.exists(path) and get_volume_name(path) == "CIRCUITPY":
                    return path
        finally:
            ctypes.windll.kernel32.SetErrorMode(old_mode)
    else:
        # No support for unknown operating systems.
        raise NotImplementedError('OS "{}" not supported.'.format(os.name))

    if device_dir:
        # Found it!
        self.connected = True
        return device_dir
    else:
        # Not plugged in? Just return Mu's regular workspace directory
        # after warning the user.
        wd = super().workspace_dir()
        if self.connected:
            m = _("Could not find an attached CircuitPython device.")
            info = _(
                "Python files for CircuitPython devices"
                " are stored on the device. Therefore, to edit"
                " these files you need to have the device plugged in."
                " Until you plug in a device, Mu will use the"
                " directory found here:\n\n"
                " {}\n\n...to store your code."
            )
            self.view.show_message(m, info.format(wd))
            self.connected = False
        return wd


# Traceback (PermissionError)
(base) me@ubuntu:~$ mu-editor
Logging to /home/me/snap/mu-editor/common/.cache/mu/log/mu.log
Gtk-Message: Failed to load module "gail"
Gtk-Message: Failed to load module "atk-bridge"
Gtk-Message: Failed to load module "canberra-gtk-module"
Traceback (most recent call last):
File "/snap/mu-editor/4/lib/python3.5/site-packages/mu/logic.py", line 1075, in select_mode
self.change_mode(new_mode)
File "/snap/mu-editor/4/lib/python3.5/site-packages/mu/logic.py", line 1112, in change_mode
self.modes[mode].workspace_dir()))
File "/snap/mu-editor/4/lib/python3.5/site-packages/mu/modes/adafruit.py", line 98, in workspace_dir
mount_output = check_output(mount_command).splitlines()
File "/snap/mu-editor/4/usr/lib/python3.5/subprocess.py", line 626, in check_output
**kwargs).stdout
File "/snap/mu-editor/4/usr/lib/python3.5/subprocess.py", line 693, in run
with Popen(*popenargs, **kwargs) as process:
File "/snap/mu-editor/4/usr/lib/python3.5/subprocess.py", line 947, in __init__
restore_signals, start_new_session)
File "/snap/mu-editor/4/usr/lib/python3.5/subprocess.py", line 1551, in _execute_child
raise child_exception_type(errno_num, err_msg)
PermissionError: [Errno 13] Permission denied

# Fixed code
def workspace_dir(self):
    """
    Return the default location on the filesystem for opening and closing
    files.
    """
    device_dir = None
    # Attempts to find the path on the filesystem that represents the
    # plugged in CIRCUITPY board.
    if os.name == "posix":
        # We're on Linux or OSX
        for mount_command in ["mount", "/sbin/mount"]:
            try:
                mount_output = check_output(mount_command).splitlines()
                mounted_volumes = [x.split()[2] for x in mount_output]
                for volume in mounted_volumes:
                    tail = os.path.split(volume)[-1]
                    if tail.startswith(b"CIRCUITPY") or tail.startswith(b"PYBFLASH"):
                        device_dir = volume.decode("utf-8")
                        break
            except FileNotFoundError:
                pass
            except PermissionError as e:
                logger.error(
                    "Received '{}' running command: {}".format(
                        repr(e),
                        mount_command,
                    )
                )
                m = _("Permission error running mount command")
                info = _(
                    'The mount command ("{}") returned an error: '
                    "{}. Mu will continue as if a device isn't "
                    "plugged in."
                ).format(mount_command, repr(e))
                self.view.show_message(m, info)
            # Avoid crashing Mu, the workspace dir will be set to default
            except Exception as e:
                logger.error(
                    "Received '{}' running command: {}".format(
                        repr(e),
                        mount_command,
                    )
                )

    elif os.name == "nt":
        # We're on Windows.

        def get_volume_name(disk_name):
            """
            Each disk or external device connected to windows has an
            attribute called "volume name". This function returns the
            volume name for the given disk/device.

            Code from http://stackoverflow.com/a/12056414
            """
            vol_name_buf = ctypes.create_unicode_buffer(1024)
            ctypes.windll.kernel32.GetVolumeInformationW(
                ctypes.c_wchar_p(disk_name),
                vol_name_buf,
                ctypes.sizeof(vol_name_buf),
                None,
                None,
                None,
                None,
                0,
            )
            return vol_name_buf.value

        #
        # In certain circumstances, volumes are allocated to USB
        # storage devices which cause a Windows popup to raise if their
        # volume contains no media. Wrapping the check in SetErrorMode
        # with SEM_FAILCRITICALERRORS (1) prevents this popup.
        #
        old_mode = ctypes.windll.kernel32.SetErrorMode(1)
        try:
            for disk in "ABCDEFGHIJKLMNOPQRSTUVWXYZ":
                path = "{}:\\".format(disk)
                if os.path.exists(path) and get_volume_name(path) == "CIRCUITPY":
                    return path
        finally:
            ctypes.windll.kernel32.SetErrorMode(old_mode)
    else:
        # No support for unknown operating systems.
        raise NotImplementedError('OS "{}" not supported.'.format(os.name))

    if device_dir:
        # Found it!
        self.connected = True
        return device_dir
    else:
        # Not plugged in? Just return Mu's regular workspace directory
        # after warning the user.
        wd = super().workspace_dir()
        if self.connected:
            m = _("Could not find an attached CircuitPython device.")
            info = _(
                "Python files for CircuitPython devices"
                " are stored on the device. Therefore, to edit"
                " these files you need to have the device plugged in."
                " Until you plug in a device, Mu will use the"
                " directory found here:\n\n"
                " {}\n\n...to store your code."
            )
            self.view.show_message(m, info.format(wd))
            self.connected = False
        return wd