python/cpython · error · ValueError

embedded null character

Error message

embedded null character

What it means

ValueError from ReaderHistory/_histline in Lib/_pyrepl/readline.py (the readline compatibility layer over pyrepl). History lines cannot contain NUL characters because the underlying history storage (mirroring GNU readline / libedit) treats them as terminators. When a line passed to add_history, replace_history_item, or internal history recording contains \0 and sanitize_nuls is False, this error is raised.

Source

Thrown at Lib/_pyrepl/readline.py:430

        pass  # XXX we don't support parsing GNU-readline-style init files

    def set_completer(self, function: Completer | None = None) -> None:
        self.config.readline_completer = function

    def get_completer(self) -> Completer | None:
        return self.config.readline_completer

    def set_completer_delims(self, delimiters: Collection[str]) -> None:
        self.config.completer_delims = frozenset(delimiters)

    def get_completer_delims(self) -> str:
        return "".join(sorted(self.config.completer_delims))

    def _histline(self, line: str, *, sanitize_nuls: bool = False) -> str:
        line = line.rstrip("\n")
        if "\0" in line:
            if not sanitize_nuls:
                raise ValueError("embedded null character")
            line = line.replace("\0", "")
        return line

    def get_history_length(self) -> int:
        return self.saved_history_length

    def set_history_length(self, length: int) -> None:
        self.saved_history_length = length

    def get_current_history_length(self) -> int:
        return len(self.get_reader().history)

    def read_history_file(self, filename: str = gethistoryfile()) -> None:
        # multiline extension (really a hack) for the end of lines that
        # are actually continuations inside a single multiline_input()
        # history item: we use \r\n instead of just \n.  If the history
        # file is passed to GNU readline, the extra \r are just ignored.
        history = self.get_reader().history

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Strip NULs before pushing to history: line.replace('\0', '') on the input string.
  2. If your tool intentionally tolerates them, call the internal API with sanitize_nuls=True where available (internal history recording path) rather than the public readline functions.
  3. Fix the upstream producer — a NUL in a command line almost always means a decoding/paste bug worth investigating.

Example fix

# before
import readline
readline.add_history(buf)  # ValueError if '\0' in buf

# after
import readline
readline.add_history(buf.replace('\0', ''))
Defensive patterns

Strategy: validation

Validate before calling

import readline

def add_history_safe(line):
    if '\0' in line:
        line = line.replace('\0', '')
    readline.add_history(line)

Try / catch

try:
    readline.add_history(line)
except ValueError as e:
    if 'embedded null' in str(e):
        readline.add_history(line.replace('\0', ''))
    else:
        raise

Prevention

When it happens

Trigger: Calling readline.add_history(line) or replacing a history item with a string containing '\0' (e.g. data read from a socket, binary-ish protocol text, or strings built with null padding); internal history append of a command line with an embedded NUL when sanitization is not enabled.

Common situations: RL-compat shims in tools like IPython/pgcli that push arbitrary captured input into history; pasting from terminals that emit NUL bytes; programs that store length-padded buffers and forget to strip trailing '\0's before adding to history.

Related errors


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