pypa/pip · error · MissingCallableSuffix
Invalid script entry point
Error message
Invalid script entry point: {entry_point} - A callable suffix is required. See https://packaging.python.org/specifications/entry-points/#use-for-scripts for more information. What it means
Raised as MissingCallableSuffix (subclass of InstallationError) by _raise_for_invalid_entrypoint when a wheel's console_scripts entry point specification lacks the ':callable' suffix. Entry points used for executable scripts must name a callable object ('module:func'); a bare 'module' is invalid and pip refuses to generate the script.
Solutions
- Fix the entry point to include the callable: 'name = package.module:func'.
- Rebuild the wheel with the corrected entry_points.
- Inspect the wheel's '*.dist-info/entry_points.txt' to confirm the ':func' suffix.
Example fix
# before [console_scripts] mycli = mypackage.cli # after [console_scripts] mycli = mypackage.cli:main
Defensive patterns
Strategy: validation
Validate before calling
from pip._internal.operations.install.wheel import _raise_for_invalid_entrypoint
def entry_point_ok(spec: str, scripts_dir: str) -> bool:
try:
_raise_for_invalid_entrypoint(spec, scripts_dir)
return True
except Exception:
return False
# assert entry_point_ok("pkg.mod:main", "/tmp/scripts")
# assert not entry_point_ok("pkg.mod", "/tmp/scripts") Prevention
- Always use the 'module:callable' form for console_scripts entries.
- Validate entry_points with a standard build backend before publishing wheels.
- Inspect *.dist-info/entry_points.txt when debugging script generation.
When it happens
Trigger: Installing a wheel whose entry_points.txt (or pyproject/setup.cfg console_scripts) has an entry without ':', e.g. 'mycli = mypackage.cli' instead of 'mycli = mypackage.cli:main'. distlib's get_export_entry returns an entry whose .suffix is None.
Common situations: Hand-edited entry_points; packaging tools/old setuptools emitting malformed entry points; tutorial copy-paste that omitted the ':func' part.
Related errors
- .dist-info directory
- .dist-info directory not found
- Invalid build number
- Invalid project name
- Invalid script entry point name
AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08).
Data as JSON: /api/errors/5ed05a3a6bdfacd7.
Report an issue: GitHub.
Appendix: source
Thrown at src/pip/_internal/operations/install/wheel.py:409
class MissingCallableSuffix(InstallationError):
def __init__(self, entry_point: str) -> None:
super().__init__(
f"Invalid script entry point: {entry_point} - A callable "
"suffix is required. See https://packaging.python.org/"
"specifications/entry-points/#use-for-scripts for more "
"information."
)
def _raise_for_invalid_entrypoint(specification: str, scripts_dir: str) -> None:
entry = get_export_entry(specification)
if entry is None:
return
if entry.suffix is None:
raise MissingCallableSuffix(str(entry))
# distlib joins the entry point name onto the scripts directory, so a name
# with path separators or ``..`` components can resolve elsewhere. The script
# must resolve to a path strictly inside the scripts directory.
dest = os.path.join(scripts_dir, entry.name)
resolves_to_scripts_dir = os.path.abspath(dest) == os.path.abspath(scripts_dir)
if resolves_to_scripts_dir or not is_within_directory(scripts_dir, dest):
raise InstallationError(
f"Invalid script entry point name {entry.name!r}: the script "
f"would be installed outside the scripts directory ({scripts_dir})."
)
class PipScriptMaker(ScriptMaker):
# Override distlib's default script template with one that
# doesn't import `re` module, allowing scripts to load faster.
script_template = textwrap.dedent("""\
import sysView on GitHub (pinned to f399c37189)