django/django · error · CommandError

Migrations can be pruned only when an app is specified.

Error message

Migrations can be pruned only when an app is specified.

What it means

The --prune flag deletes entries from the django_migrations database table for migration files that no longer exist on disk. Because pruning operates per-app (it scopes to `migration[0] == app_label` at line 201), an app label is mandatory. Without one, the command cannot determine scope and refuses at line 191-193.

Source

Thrown at django/core/management/commands/migrate.py:192

                # graph, use the last replacement instead.
                if (
                    target not in executor.loader.graph.nodes
                    and target in executor.loader.replacements
                ):
                    incomplete_migration = executor.loader.replacements[target]
                    target = incomplete_migration.replaces[-1]
                targets = [target]
            target_app_labels_only = False
        elif options["app_label"]:
            targets = [
                key for key in executor.loader.graph.leaf_nodes() if key[0] == app_label
            ]
        else:
            targets = executor.loader.graph.leaf_nodes()

        if options["prune"]:
            if not options["app_label"]:
                raise CommandError(
                    "Migrations can be pruned only when an app is specified."
                )
            if self.verbosity > 0:
                self.stdout.write("Pruning migrations:", self.style.MIGRATE_HEADING)
            to_prune = sorted(
                migration
                for migration in set(executor.loader.applied_migrations)
                - set(executor.loader.disk_migrations)
                if migration[0] == app_label
            )
            squashed_migrations_with_deleted_replaced_migrations = [
                migration_key
                for migration_key, migration_obj in executor.loader.replacements.items()
                if any(replaced in to_prune for replaced in migration_obj.replaces)
            ]
            if squashed_migrations_with_deleted_replaced_migrations:
                self.stdout.write(
                    self.style.NOTICE(

View on GitHub (pinned to ae25a40be0)

Solutions

  1. Specify the app: `python manage.py migrate myapp --prune`
  2. Run --prune for each app that has stale migration records individually

Example fix

// before
python manage.py migrate --prune
// after
python manage.py migrate myapp --prune
Defensive patterns

Strategy: validation

Validate before calling

from django.core.management import call_command

app_label = "myapp"
if not app_label:
    raise ValueError("--prune requires an app label; specify one")
call_command("migrate", app_label, prune=True)

Type guard

def has_app_label_for_prune(app_label) -> bool:
    """True if a non-empty app label is provided for --prune."""
    return isinstance(app_label, str) and bool(app_label.strip())

Try / catch

from django.core.management import call_command
from django.core.management.base import CommandError

try:
    call_command("migrate", prune=True)
except CommandError as e:
    if "pruned only when an app is specified" in str(e):
        raise ValueError("Specify an app label: migrate myapp --prune")
    raise

Prevention

When it happens

Trigger: Running `python manage.py migrate --prune` without specifying an app label as the first positional argument.

Common situations: Developer reads about --prune in docs and runs it globally without an app, not realizing it requires a per-app scope.

Related errors


AI-assisted analysis of django/django@ae25a40be0 (2026-08-06). Data as JSON: /api/errors/0c28fbc250643feb. Report an issue: GitHub.