python/cpython · error · ValueError

No history item at position %d

Error message

No history item at position %d

What it means

ValueError from remove_history_item in Lib/_pyrepl/readline.py. Unlike get_history_item, which uses readline's 1-based convention, remove_history_item indexes the reader's history list 0-based and requires 0 <= index < len(history). Any out-of-range index raises 'No history item at position %d', mirroring GNU readline's error.

Source

Thrown at Lib/_pyrepl/readline.py:513

                f.write(entry + "\n")
        self.set_history_length(saved_length + length)

    def clear_history(self) -> None:
        del self.get_reader().history[:]

    def get_history_item(self, index: int) -> str | None:
        history = self.get_reader().history
        if 1 <= index <= len(history):
            return history[index - 1]
        else:
            return None  # like readline.c

    def remove_history_item(self, index: int) -> None:
        history = self.get_reader().history
        if 0 <= index < len(history):
            del history[index]
        else:
            raise ValueError("No history item at position %d" % index)
            # like readline.c

    def replace_history_item(self, index: int, line: str) -> None:
        history = self.get_reader().history
        if 0 <= index < len(history):
            history[index] = self._histline(line)
        else:
            raise ValueError("No history item at position %d" % index)
            # like readline.c

    def add_history(self, line: str) -> None:
        self.get_reader().history.append(self._histline(line))

    def set_startup_hook(self, function: Callback | None = None) -> None:
        self.startup_hook = function

    def get_line_buffer(self) -> str:
        return self.get_reader().get_unicode()

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Use 0-based indices and clamp to current length: 0 <= i < readline.get_current_history_length().
  2. When deleting multiple items, delete from the end downward (for i in range(n-1, -1, -1)) so indices stay valid, or re-read the length each iteration.
  3. Remember the asymmetry: get_history_item is 1-based, remove/replace are 0-based — same as GNU readline.

Example fix

# before
for i in range(1, readline.get_current_history_length()):
    readline.remove_history_item(i)  # ValueError on later iterations

# after
for i in range(readline.get_current_history_length() - 1, -1, -1):
    readline.remove_history_item(i)
Defensive patterns

Strategy: validation

Validate before calling

import readline

def remove_history_safe(i):
    if 0 <= i < readline.get_current_history_length():
        readline.remove_history_item(i)
    else:
        raise ValueError(f'no history item {i}')

Prevention

When it happens

Trigger: readline.remove_history_item(i) with i >= len(history) or i < 0. Classic trap: iterating with 1-based indices taken from get_current_history_length() or code written against get_history_item's 1-based API.

Common situations: Porting readline C or ctypes code that mixes 1-based and 0-based conventions; popping history entries in a loop with a stale length after each removal (length shrinks as you delete); off-by-one when deleting the last item (index == length).

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/1c370c4cc91ae551. Report an issue: GitHub.