diff --git a/src/poetry/console/commands/update.py b/src/poetry/console/commands/update.py
index b749b01ab6c..d1dec7e80fc 100644
--- a/src/poetry/console/commands/update.py
+++ b/src/poetry/console/commands/update.py
@@ -1,10 +1,13 @@
from __future__ import annotations
+import re
+
from typing import TYPE_CHECKING
from typing import ClassVar
from cleo.helpers import argument
from cleo.helpers import option
+from packaging.utils import canonicalize_name
from poetry.console.commands.installer_command import InstallerCommand
@@ -13,6 +16,8 @@
from cleo.io.inputs.argument import Argument
from cleo.io.inputs.option import Option
+_VERSION_SPECIFIER_RE = re.compile(r"[><=!~]")
+
class UpdateCommand(InstallerCommand):
name = "update"
@@ -45,6 +50,39 @@ class UpdateCommand(InstallerCommand):
def handle(self) -> int:
packages = self.argument("packages")
if packages:
+ # Detect version specifiers in package arguments — poetry update
+ # only accepts bare package names, not requirement strings.
+ packages_with_specifiers = [
+ p for p in packages if _VERSION_SPECIFIER_RE.search(p)
+ ]
+ if packages_with_specifiers:
+ self.line_error(
+ "Version specifiers are not allowed in"
+ " poetry update."
+ )
+ for pkg in packages_with_specifiers:
+ self.line_error(f" - {pkg}")
+ self.line_error(
+ "Use poetry add to change version constraints."
+ )
+ return 1
+
+ # Validate that all specified packages are declared dependencies
+ all_dependencies = {
+ canonicalize_name(dep.name) for dep in self.poetry.package.all_requires
+ }
+
+ invalid_packages = [
+ p for p in packages if canonicalize_name(p) not in all_dependencies
+ ]
+
+ if invalid_packages:
+ self.line_error(
+ "The following packages are not dependencies"
+ f" of this project: {', '.join(invalid_packages)}"
+ )
+ return 1
+
self.installer.whitelist(dict.fromkeys(packages, "*"))
self.installer.only_groups(self.activated_groups)
diff --git a/tests/console/commands/test_update.py b/tests/console/commands/test_update.py
index 6b48071d612..3f8da1d87ee 100644
--- a/tests/console/commands/test_update.py
+++ b/tests/console/commands/test_update.py
@@ -100,3 +100,72 @@ def test_update_sync_option_is_passed_to_the_installer(
tester.execute("--sync")
assert tester.command.installer._requires_synchronization
+
+
+def test_update_with_invalid_package_name_shows_error(
+ poetry_with_outdated_lockfile: Poetry,
+ command_tester_factory: CommandTesterFactory,
+) -> None:
+ """
+ Providing non-existent package names should raise an error.
+ """
+ tester = command_tester_factory("update", poetry=poetry_with_outdated_lockfile)
+
+ status = tester.execute("nonexistent-package")
+
+ assert status == 1
+ assert (
+ "The following packages are not dependencies of this project: nonexistent-package"
+ in tester.io.fetch_error()
+ )
+
+
+def test_update_with_multiple_invalid_package_names_shows_error(
+ poetry_with_outdated_lockfile: Poetry,
+ command_tester_factory: CommandTesterFactory,
+) -> None:
+ """
+ Providing multiple non-existent package names should list all of them in the error.
+ """
+ tester = command_tester_factory("update", poetry=poetry_with_outdated_lockfile)
+
+ status = tester.execute("fake1 fake2 fake3")
+
+ assert status == 1
+ error = tester.io.fetch_error()
+ assert "The following packages are not dependencies of this project" in error
+ assert "fake1" in error
+ assert "fake2" in error
+ assert "fake3" in error
+
+
+@pytest.mark.parametrize(
+ "package_spec",
+ [
+ "docker==1.2.3",
+ "docker>=1.0,<2.0",
+ "docker!=1.0",
+ "docker[extra]>=1.0",
+ "nonexistent==1.2.3",
+ "nonexistent>=1.0",
+ ],
+)
+def test_update_with_version_specifier_raises_error(
+ package_spec: str,
+ poetry_with_outdated_lockfile: Poetry,
+ command_tester_factory: CommandTesterFactory,
+) -> None:
+ """
+ The update command only accepts bare package names. Passing requirement
+ strings with version specifiers should raise a clear error pointing
+ to poetry add, regardless of whether the package is a dependency.
+ """
+ tester = command_tester_factory("update", poetry=poetry_with_outdated_lockfile)
+
+ status = tester.execute(package_spec)
+
+ assert status == 1
+ error = tester.io.fetch_error()
+ assert "Version specifiers are not allowed" in error
+ assert "poetry update" in error
+ assert "poetry add" in error