kovidgoyal/kitty · warning

Refusing to read image file as permission was denied

Error message

Refusing to read image file as permission was denied

What it means

kitty refused to transmit an image file because the Python-side is_ok_to_read_image_file check returned non-True - the user declined the permission prompt or the allow-list denied the file. The graphics command is aborted with EPERM.

Source

Thrown at kitty/graphics.c:708

                load_data->loading_for = (const ImageAndFrame){0};
            }
            break;
        case 'f': // file
        case 't': // temporary file
        case 's': // POSIX shared memory
            if (g->payload_sz > 2048) ABRT("EINVAL", "Filename too long");
            snprintf(fname, sizeof(fname) / sizeof(fname[0]), "%.*s", (int)g->payload_sz, payload);
            if (transmission_type == 's') fd = safe_shm_open(fname, O_RDONLY, 0);
            else fd = safe_open(fname, O_CLOEXEC | O_RDONLY | O_NONBLOCK, 0); // O_NONBLOCK so that opening a FIFO pipe does not block
            if (fd == -1) ABRT("EBADF", "Failed to open file for graphics transmission with error: [%d] %s", errno, strerror(errno));
            if (global_state.boss && transmission_type != 's') {
                RAII_PyObject(cret_, PyObject_CallMethod(global_state.boss, "is_ok_to_read_image_file", "si", fname, fd));
                if (cret_ == NULL) {
                    PyErr_Print();
                    ABRT("EBADF", "Failed to check file for read permission");
                }
                if (cret_ != Py_True) {
                    log_error("Refusing to read image file as permission was denied");
                    ABRT("EPERM", "Permission denied to read image file");
                }
            }
            // When the data needs further processing the entire (possibly
            // compressed) payload is needed, otherwise reading more than the
            // expected number of bytes is pointless.
            const size_t max_to_read = (g->compressed || data_fmt == PNG) ? MAX_DATA_SZ : load_data->data_sz;
            load_data->loading_completed_successfully = read_img_file(self, fd, g->data_sz, g->data_offset, max_to_read, transmission_type == 's');
            safe_close(fd, __FILE__, __LINE__);
            if (transmission_type == 't' && strstr(fname, "tty-graphics-protocol") != NULL) {
                if (global_state.boss) {
                    call_boss(safe_delete_temp_file, "s", fname);
                } else unlink(fname);
            } else if (transmission_type == 's') shm_unlink(fname);
            if (!load_data->loading_completed_successfully) return NULL;
            break;
        default: ABRT("EINVAL", "Unknown transmission type: %c", g->transmission_type);
    }

View on GitHub (pinned to 6d5d0c4406)

Solutions

  1. Answer 'yes' or 'always' when kitty asks permission to read the file
  2. Add the directory to kitty's file-read allow list in the permission prompt or kitty.conf
  3. Verify the path is readable by the kitty process
  4. For scripting, pre-approve directories via the permissions config instead of relying on the prompt

Example fix

# kitty.conf
# before (default: ask)
# after
allow_file_read /home/user/Pictures
Defensive patterns

Strategy: validation

Prevention

When it happens

Trigger: Sending a graphics protocol transmit-file command (t=t) whose path fails kitty's file-read permission policy (ask/deny configuration).

Common situations: An app or kitten displays an image from a path outside allowed directories; the user answered 'no' to the read-permission prompt; file-read allow list too restrictive.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of kovidgoyal/kitty@6d5d0c4406 (2026-08-27). Data as JSON: /api/errors/b83717fe0db3b7f7. Report an issue: GitHub.