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
- Answer 'yes' or 'always' when kitty asks permission to read the file
- Add the directory to kitty's file-read allow list in the permission prompt or kitty.conf
- Verify the path is readable by the kitty process
- 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
- Pre-approve image directories in kitty's file permissions config
- When scripting graphics transmission, check for the EPERM abort code in the terminal response
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
- EPERM
- This kitten should be run as: kitten quick-access-terminal
- Incorrect owner on pwfile: uid={shm.stats.st_uid} gid={shm.s
- failed to read the directory %s with error: %w
- failed to read from file: %#v with error: %w
AI-assisted analysis of kovidgoyal/kitty@6d5d0c4406 (2026-08-27).
Data as JSON: /api/errors/b83717fe0db3b7f7.
Report an issue: GitHub.