ErrLookup › Background articles › CommandError — when a CLI or management command stops cleanly with a message instead of a stack trace
CommandError — when a CLI or management command stops cleanly with a message instead of a stack trace
CommandError is the exception that command-line tools and Django management commands raise when an invocation is invalid or cannot be completed: conflicting flags, a bad option value, a missing prerequisite, a name that will not work, or a wrapped failure from an external tool. A developer meets it at the terminal as a printed, human-readable message — often colored red and without a traceback — rather than as a crash. The same name, CommandError, is used by Django (the canonical source), pip, Expo, Alembic, and projects built on Django such as Plane, each raising it at the user-facing boundary to say the request itself is wrong, not that the program has a bug.
Distilled from 193 documented records across 6 repositories.
Background
CommandError lives at the boundary between the operator and the tool: the command-parsing and option-validation layer, and the early setup phase of a management command. Django defines the canonical form as a dedicated exception that management commands raise to signal a controlled stop. The intent is always the same — fail with a message written for a human, printed to the console (Expo prints it red and suppresses the stack trace; Django exits non-zero unless the error is caught), instead of letting an unexpected traceback reach the user. This separates a bad request from a defect in the tool itself.
How the error is produced varies by library but follows a small number of shapes. Validation guards raise it up front, before any real work: pip's cmdoptions.py callbacks reject mutually exclusive flags and malformed option values, Django's TemplateCommand.validate_name rejects names that are not valid Python identifiers or that collide with importable modules, and Django's createsuperuser refuses an explicitly blank username. Wrapper guards raise it after a deeper failure has already happened: pip's parse_command wraps a subprocess failure when re-invoking a target interpreter, Alembic's template_to_file catches any Mako rendering exception and points at a detailed traceback file, and Django's dumpdata catches any exception during serialization and re-raises it as a clean CommandError (revealing the original only with --traceback). State guards raise it when a required precondition is absent: Plane's management commands check for an existing user or membership row, Expo checks for an installed development build or a missing package, and Django's makemessages checks that a locale path exists to write into.
The quality and reachability of the message differs across libraries in ways a developer should know. pip and Django messages are precise and frequently embed the fix (pip's option-conflict messages name exactly which flags clash and what to drop; Django's migration-conflict message tells the operator to run makemigrations --merge). Expo's messages are terminal-oriented and may bundle a full install command or a learn-more link. Plane's records show a distinct hazard: several management commands wrap their whole handler in a bare except Exception and re-raise a generic message, which masks the specific reason — for example, "already an admin" is replaced by "Failed to create the instance admin," and a successful no-op repair surfaces as "No duplicate issues found." Where wrapping occurs, the actionable detail often lives in a referenced file (Expo's xcodebuild logs, Alembic's template traceback file) or behind a flag (Django's --traceback), not in the printed line itself.
Common causes
- Mutually exclusive or conflicting command-line options.The operator passed two flags that cannot both hold. pip rejects many such pairs: --only-dependencies with --no-deps, -r, --group, or the legacy resolver; --require-hashes with --no-require-hashes; --build-constraint with --no-build-isolation; --pre with --all-releases or --only-final. Each combination is semantically contradictory, so pip stops before doing any work and names the conflict.
- Missing prerequisite state or data.The command needs something to exist before it can run, and it does not. Plane aborts when the seeding email matches no signed-in User row or when a workspace membership was never created; Expo throws when no development build is installed on the device or when the expo package (and its bundledNativeModules.json) is absent; Django's makemessages stops when there is no locale directory and no LOCALE_PATHS to write the extracted POT file into.
- Invalid names, identifiers, or argument values.A value the operator supplied will not work as given. Django's startproject and startapp refuse names that are not valid Python identifiers (hyphens, leading digits, dots, spaces) and names that collide with an already-importable module; Django's createsuperuser treats an explicitly blank username as a hard error; Expo's config --type rejects values outside the allowed set. These guards fire before any file or resource is created.
- An external tool or subprocess failure wrapped as a clean message.A downstream process failed and the command surfaces it as CommandError rather than letting the raw failure propagate. pip wraps a failed re-invocation of a target interpreter; Expo wraps an xcodebuild exit with a pointer to the log files it wrote; Alembic wraps any Mako template-rendering exception and writes a template-oriented traceback to a temp file. The underlying cause is real, but the printed message is the sanitized version.
- Missing or malformed command input.The invocation itself is incomplete or misshaped. pip raises CommandError when no requirement is given alongside --find-links (it assumes a forgotten package name), when the subcommand is an unknown typo (with a suggested correction), or when --refresh-package consumes the next flag as its value because a real name was omitted. The operator usually forgot an argument or mistyped a command.
- Conflicting migration graph or state-graph divergence.Django's makemigrations detects multiple leaf nodes in a single app's migration graph — the result of two developers branching migrations from the same base — and stops, directing the operator to makemigrations --merge. This is a structural conflict in stored state rather than a bad flag, but it is surfaced the same way: a clean message naming the offending apps.
What usually fixes it
- Read the printed message closely: CommandError messages are written for the operator and usually name the exact conflict and, in pip and Django, the specific flag or command to change. The line you see is frequently the whole diagnosis.
- Run the command's --help or list valid subcommands (pip help, expo config --help) to confirm the supported options, values, and names for your installed version — valid sets change across releases, and a renamed subcommand or a new --type value is a common source of the error.
- Audit inherited configuration for stray conflicting flags: pip.conf and PIP_* environment variables can silently supply --require-hashes or --no-require-hashes that clashes with a CLI argument, producing a conflict the operator did not type. Check equivalent config files and env vars for the tool in question.
- Validate inputs at the boundary in scripts before invoking the command: only pass --username when the variable is non-empty, confirm a project name passes str.isidentifier() before startproject, ensure a target interpreter is runnable before --python, and pre-install required packages in CI before invoking Expo commands.
- Surface the underlying cause when the message is a wrapper: pass --traceback to Django's dumpdata to see the original serialization exception, open the log files Expo points at (.expo/xcodebuild.log, xcodebuild-error.log) for native build diagnostics, and open the temp file Alembic references for the template-oriented traceback. Re-run the failing external tool directly (the target interpreter, xcodebuild) to reproduce the raw error.
- Distinguish a real failure from an informational no-op: some CommandErrors report that there is nothing to do rather than that something broke — Plane's "No duplicate issues found" and the masked "already an admin" are examples. Confirm the precondition with a direct query (a model .exists() check, a count) before treating the message as a defect.
Documented occurrences
- Cannot use '--only-dependencies' in combination with {conflict_message}. If this is unexpected, please refer to the user guide: https://pip.pypa.io/en/stable/user_guide/#installing-only-dependencies(pypa/pip)
- Invalid option: --type ${options.type}. Valid options are: public, prebuild(expo/expo)
- User email is required and should have signed in plane(makeplane/plane)
- When restricting platform and interpreter constraints using --python-version, --platform, --abi, or --implementation, either --no-deps must be set, or --only-binary=:all: must be set and --no-binary must not be set (or must be set to :none:).(pypa/pip)
- Platform and interpreter constraints using --python-version, --platform, --abi, or --implementation, are not supported when selecting requirements from {filename!r}(pypa/pip)
- Failed to run pip under {interpreter}: {exc}(pypa/pip)
- Failed to write generated server origin to app.json because the file is dynamic and does not extend the static config. The client will not be able to make server requests to API routes or static files. You can disable server linking with EXPO_NO_DEPLOY=1 or by disabling server output in the app.json.(expo/expo)
- ${title + 'Install ' + readableMissingPackages + ' by running:\n\n ' + installCommand + '\n\n' + disableMessage + '\n'}(expo/expo)
- --requirements-from-script can only be given once(pypa/pip)
- %s cannot be blank.(django/django)
- '{name}' is not a valid {app} {type}. Please make sure the {type} is a valid identifier.(django/django)
- The provided email is already an instance admin.(makeplane/plane)
- Unable to find a locale path to store translations for file %s. Make sure the 'locale' directory exists in an app or LOCALE_PATHS setting is set.(django/django)
- --require-hashes and --no-require-hashes are mutually exclusive(pypa/pip)
- Error: User {email} is not a member of workspace {slug}(makeplane/plane)
- The dependency map {bold expo/bundledNativeModules.json} cannot be found, please ensure you have the package "{bold expo}" installed in your project.(expo/expo)
- No duplicate issues found with the given identifier(makeplane/plane)
- Template rendering failed; see %s for a template-oriented traceback.(sqlalchemy/alembic)
- No development build (${customAppId}) for this project is installed. Install a development build on the target device and try again. ${learnMore('https://docs.expo.dev/development/build/')}(expo/expo)
- --refresh-package option requires 1 argument.(pypa/pip)
…and 173 more across the corpus — use search.
Honest provenance: generated on 2026-08-13 from AI-assisted analysis of the linked records. See how records are made.