sqlalchemy/alembic · error · CommandError

Error executing editor (%s)

Error message

Error executing editor (%s)

What it means

Raised as CommandError in open_in_editor (editor.py:35) wrapping ANY exception that escapes _find_editor or subprocess.check_call when launching the configured editor. It re-raises the original as __cause__ so the underlying failure (FileNotFoundError, non-zero editor exit, permission error) is preserved in the traceback.

Source

Thrown at alembic/util/editor.py:35

    """
    Opens the given file in a text editor. If the environment variable
    ``EDITOR`` is set, this is taken as preference.

    Otherwise, a list of commonly installed editors is tried.

    If no editor matches, an :py:exc:`OSError` is raised.

    :param filename: The filename to open. Will be passed  verbatim to the
        editor command.
    :param environ: An optional drop-in replacement for ``os.environ``. Used
        mainly for testing.
    """
    env = os.environ if environ is None else environ
    try:
        editor = _find_editor(env)
        check_call([editor, filename])
    except Exception as exc:
        raise CommandError("Error executing editor (%s)" % (exc,)) from exc


def _find_editor(environ: Mapping[str, str]) -> str:
    candidates = _default_editors()
    for i, var in enumerate(("EDITOR", "VISUAL")):
        if var in environ:
            user_choice = environ[var]
            if exists(user_choice):
                return user_choice
            if os.sep not in user_choice:
                candidates.insert(i, user_choice)

    for candidate in candidates:
        path = _find_executable(candidate, environ)
        if path is not None:
            return path
    raise OSError(
        "No suitable editor found. Please set the "

View on GitHub (pinned to 44fb345033)

Solutions

  1. Check the wrapped exception (the '(FileNotFoundError ...)' or exit-code text) to see what actually failed.
  2. Set EDITOR to a blocking terminal editor (e.g. nano, vim) for non-GUI environments: `export EDITOR=nano`.
  3. Ensure an interactive TTY is available (run in a real terminal, not a detached CI shell) when invoking edit.
  4. Verify the migration file the editor was asked to open actually exists.

Example fix

# before: EDITOR points to a non-blocking GUI editor that exits immediately
export EDITOR=code
alembic edit abc123
# after: use a blocking editor
export EDITOR=nano
alembic edit abc123
Defensive patterns

Strategy: try-catch

Validate before calling

import os, shutil
def editor_is_callable() -> bool:
    ed = os.environ.get('EDITOR') or os.environ.get('VISUAL')
    return bool(ed and (os.path.exists(ed) or shutil.which(ed)))

if not editor_is_callable():
    raise SystemExit('EDITOR not usable; set it before running alembic edit')

Try / catch

from alembic.util.exc import CommandError
try:
    command.edit(cfg, revision)
except CommandError as e:
    if 'Error executing editor' in str(e):
        print(f'Editor failed: {e.__cause__}. Set EDITOR to a blocking terminal editor.')
    else:
        raise

Prevention

When it happens

Trigger: Running `alembic edit <revision>` or any code path that calls open_in_editor when the editor binary exists but fails to run, exits non-zero, or the target file path is invalid. The message interpolates the wrapped exception's repr.

Common situations: EDITOR set to a GUI editor (code, subl) that returns before the file is closed and Alembic reads it; editor exits non-zero due to a config problem; the migration file path passed to the editor is missing or unreadable; running in a container without an interactive TTY.

Related errors


AI-assisted analysis of sqlalchemy/alembic@44fb345033 (2026-08-04). Data as JSON: /data/errors/1df2bf42f5121016.json. Report an issue: GitHub.