sqlalchemy/alembic · error · CommandError

Error executing editor

Error message

Error executing editor (%s)

What it means

Raised as CommandError by open_in_editor when either _find_editor or the subprocess.check_call to launch the editor raises any exception (editor.py:31-35). This wraps the underlying error — which could be the editor binary not found, the editor exiting non-zero, the file path being invalid, or permission errors — into Alembic's CommandError with the original exception chained via 'from exc'.

Solutions

  1. Check the chained exception (raise ... from exc) for the root cause — run with --verbose or inspect the traceback.
  2. Verify the EDITOR/VISUAL env var points to a working editor: echo $EDITOR && $EDITOR --version.
  3. If the editor exited non-zero intentionally (e.g., vim :cq), fix the editor session or use a different editor.
  4. Ensure the migration file path is accessible and writable.
  5. Set EDITOR to a simple known-good editor like 'nano' or 'vim' to isolate the issue.

Example fix

# before: EDITOR points to a broken/custom wrapper
export EDITOR='/usr/local/bin/my-broken-editor'
alembic edit abc123

# after
export EDITOR='vim'
alembic edit abc123
Defensive patterns

Strategy: try-catch

Validate before calling

import os
from os.path import exists

def validate_editor_available(environ=None):
    env = environ or os.environ
    for var in ('EDITOR', 'VISUAL'):
        if var in env and (exists(env[var]) or env[var]):
            return True
    from alembic.util.editor import _default_editors
    for candidate in _default_editors():
        for p in env.get('PATH', '').split(os.pathsep):
            if exists(os.path.join(p, candidate)):
                return True
    return False

Try / catch

from alembic.util.exc import CommandError

try:
    util.open_in_editor(filepath)
except CommandError as e:
    # inspect e.__cause__ for the underlying error
    cause = e.__cause__
    print(f"Editor failed: {cause}")
    if isinstance(cause, OSError) and 'No suitable editor' in str(cause):
        os.environ['EDITOR'] = 'vim'
        util.open_in_editor(filepath)
    else:
        raise

Prevention

When it happens

Trigger: Calling alembic edit <revision> (which calls util.open_in_editor at command.py:826) when the editor process fails. Also triggered by 'alembic revision' in edit mode. The try/except at editor.py:31-35 catches all Exception subclasses including OSError, CalledProcessError, and FileNotFoundError.

Common situations: The EDITOR/VISUAL env var points to a binary that crashes on the file. The editor exits with a non-zero status (e.g., vim :cq). The file path contains spaces or special characters not properly escaped. Permission denied on the migration file. Editor binary exists in EDITOR but fails at runtime.

Related errors


AI-assisted analysis of sqlalchemy/alembic@5551b5d35f (2026-08-11). Data as JSON: /api/errors/1df2bf42f5121016. Report an issue: GitHub.

Appendix: 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 5551b5d35f)