wezterm/wezterm · error · anyhow::Error

attempted to copy frame {} which is outside range 1-{}

Error message

attempted to copy frame {} which is outside range 1-{}

What it means

When appending a new frame to an existing animated image (ImageDataType::AnimRgba8), the kitty protocol lets the new frame copy its base from an earlier frame (base_frame, the 'c' key in the escape sequence). WezTerm validates that base_frame is within 1..=frames.len(); otherwise it bails with this message before compositing. It fires only on the append path (frame_number is len+1 or None), where a base frame is requested with an out-of-range index.

Source

Thrown at term/src/terminalstate/kitty.rs:691

                    ),
                }
            }
            ImageDataType::AnimRgba8 {
                width,
                height,
                frames,
                durations,
                hashes,
            } => {
                let frame_no = frame.frame_number.unwrap_or(frames.len() as u32 + 1);
                if frame_no == frames.len() as u32 + 1 {
                    // Append a new frame

                    let mut new_frame = match frame.base_frame {
                        None => RgbaImage::from_pixel(*width, *height, background_pixel),
                        Some(n) => {
                            let n = n as usize;
                            anyhow::ensure!(
                                n > 0 && n <= frames.len(),
                                "attempted to copy frame {} which is outside range 1-{}",
                                n,
                                frames.len()
                            );
                            RgbaImage::from_vec(*width, *height, frames[n - 1].clone()).unwrap()
                        }
                    };

                    blit(&mut new_frame, &img, x, y, frame.composition_mode)?;

                    let new_frame_data = new_frame.into_vec();
                    let new_frame_hash = ImageDataType::hash_bytes(&new_frame_data);

                    frames.push(new_frame_data);
                    hashes.push(new_frame_hash);
                    durations.push(frame_gap);
                } else {

View on GitHub (pinned to 9c04f79f86)

Solutions

  1. Use 1-based frame numbers for the c= (base frame) key: valid values are 1 through the current frame count
  2. Query the image (a=q,i=<id>) or locally track the frame count and clamp base_frame to that range before transmitting
  3. If the animation state is uncertain, retransmit the full animation from frame 1 under a fresh image id

Example fix

# before: 0-based base frame index
print(f"\x1b_Gf=4,c={idx},a=t,i=5;{payload}\x1b\\")  # fails when idx == 0 or idx > frame_count

# after: clamp to valid 1-based range
base = min(max(idx, 1), frame_count)
print(f"\x1b_Gf=4,c={base},a=t,i=5;{payload}\x1b\\")
Defensive patterns

Strategy: validation

Validate before calling

# base frame (c= key) must be 1..=frame_count when appending
if base_frame is not None and not (1 <= base_frame <= frame_count):
    raise ValueError(f"base frame {base_frame} out of range 1-{frame_count}")

Type guard

def valid_base_frame(base: int | None, frame_count: int) -> bool:
    return base is None or (1 <= base <= frame_count)

Prevention

When it happens

Trigger: Sending `<ESC>_Gc=N,f=F,a=t,...` where N is 0 or greater than the current frame count of image id, while F addresses a new frame (frame_number omitted or equal to current frame count + 1). Zero-indexed c=0 is the classic trigger since kitty frame numbers are 1-based.

Common situations: Senders that use 0-based frame indices while the kitty spec is 1-based; stale animation state where the image was replaced by a shorter one under the same id; scripts that hard-code a base frame after trimming frames elsewhere.

Related errors


AI-assisted analysis of wezterm/wezterm@9c04f79f86 (2026-08-16). Data as JSON: /api/errors/ea74c4d800f998c8. Report an issue: GitHub.