diff --git a/crates/uv/src/commands/project/export.rs b/crates/uv/src/commands/project/export.rs
index 25d1f45f4640..20fca2bce6da 100644
--- a/crates/uv/src/commands/project/export.rs
+++ b/crates/uv/src/commands/project/export.rs
@@ -73,7 +73,7 @@ pub(crate) async fn export(
     };
 
     // Determine the default groups to include.
-    validate_dependency_groups(project.pyproject_toml(), &dev)?;
+    validate_dependency_groups(&project, &dev)?;
     let defaults = default_dependency_groups(project.pyproject_toml())?;
 
     let VirtualProject::Project(project) = project else {
diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs
index 02854f8206cf..d11320c1ac0b 100644
--- a/crates/uv/src/commands/project/mod.rs
+++ b/crates/uv/src/commands/project/mod.rs
@@ -38,7 +38,7 @@ use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy};
 use uv_warnings::{warn_user, warn_user_once};
 use uv_workspace::dependency_groups::DependencyGroupError;
 use uv_workspace::pyproject::PyProjectToml;
-use uv_workspace::Workspace;
+use uv_workspace::{VirtualProject, Workspace};
 
 use crate::commands::pip::loggers::{InstallLogger, ResolveLogger};
 use crate::commands::pip::operations::{Changelog, Modifications};
@@ -126,7 +126,10 @@ pub(crate) enum ProjectError {
     ),
 
     #[error("Group `{0}` is not defined in the project's `dependency-group` table")]
-    MissingGroup(GroupName),
+    MissingGroupProject(GroupName),
+
+    #[error("Group `{0}` is not defined in any project's `dependency-group` table")]
+    MissingGroupWorkspace(GroupName),
 
     #[error("Default group `{0}` (from `tool.uv.default-groups`) is not defined in the project's `dependency-group` table")]
     MissingDefaultGroup(GroupName),
@@ -1366,7 +1369,7 @@ pub(crate) async fn script_python_requirement(
 /// Validate the dependency groups requested by the [`DevGroupsSpecification`].
 #[allow(clippy::result_large_err)]
 pub(crate) fn validate_dependency_groups(
-    pyproject_toml: &PyProjectToml,
+    project: &VirtualProject,
     dev: &DevGroupsSpecification,
 ) -> Result<(), ProjectError> {
     for group in dev
@@ -1374,12 +1377,38 @@ pub(crate) fn validate_dependency_groups(
         .into_iter()
         .flat_map(GroupsSpecification::names)
     {
-        if !pyproject_toml
-            .dependency_groups
-            .as_ref()
-            .is_some_and(|groups| groups.contains_key(group))
-        {
-            return Err(ProjectError::MissingGroup(group.clone()));
+        match project {
+            VirtualProject::Project(project) => {
+                // The group must be defined in the target project.
+                if !project
+                    .current_project()
+                    .pyproject_toml()
+                    .dependency_groups
+                    .as_ref()
+                    .is_some_and(|groups| groups.contains_key(group))
+                {
+                    return Err(ProjectError::MissingGroupProject(group.clone()));
+                }
+            }
+            VirtualProject::NonProject(workspace) => {
+                // The group must be defined in at least one workspace package.
+                if !workspace
+                    .pyproject_toml()
+                    .dependency_groups
+                    .as_ref()
+                    .is_some_and(|groups| groups.contains_key(group))
+                {
+                    if workspace.packages().values().all(|package| {
+                        !package
+                            .pyproject_toml()
+                            .dependency_groups
+                            .as_ref()
+                            .is_some_and(|groups| groups.contains_key(group))
+                    }) {
+                        return Err(ProjectError::MissingGroupWorkspace(group.clone()));
+                    }
+                }
+            }
         }
     }
     Ok(())
diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs
index 0157a9c60b4a..6f20a4df1621 100644
--- a/crates/uv/src/commands/project/run.rs
+++ b/crates/uv/src/commands/project/run.rs
@@ -551,7 +551,7 @@ pub(crate) async fn run(
                 }
             } else {
                 // Determine the default groups to include.
-                validate_dependency_groups(project.pyproject_toml(), &dev)?;
+                validate_dependency_groups(&project, &dev)?;
                 let defaults = default_dependency_groups(project.pyproject_toml())?;
 
                 // Determine the lock mode.
diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs
index 418b6b30b847..7070db979936 100644
--- a/crates/uv/src/commands/project/sync.rs
+++ b/crates/uv/src/commands/project/sync.rs
@@ -96,7 +96,7 @@ pub(crate) async fn sync(
     }
 
     // Determine the default groups to include.
-    validate_dependency_groups(project.pyproject_toml(), &dev)?;
+    validate_dependency_groups(&project, &dev)?;
     let defaults = default_dependency_groups(project.pyproject_toml())?;
 
     // Discover or create the virtual environment.
diff --git a/crates/uv/src/commands/project/tree.rs b/crates/uv/src/commands/project/tree.rs
index c9ef07ee45c2..c40392402309 100644
--- a/crates/uv/src/commands/project/tree.rs
+++ b/crates/uv/src/commands/project/tree.rs
@@ -9,7 +9,7 @@ use uv_configuration::{Concurrency, DevGroupsSpecification, LowerBound, TargetTr
 use uv_pep508::PackageName;
 use uv_python::{PythonDownloads, PythonPreference, PythonRequest, PythonVersion};
 use uv_resolver::TreeDisplay;
-use uv_workspace::{DiscoveryOptions, Workspace};
+use uv_workspace::{DiscoveryOptions, VirtualProject, Workspace};
 
 use crate::commands::pip::loggers::DefaultResolveLogger;
 use crate::commands::pip::resolution_markers;
@@ -50,7 +50,7 @@ pub(crate) async fn tree(
     let workspace = Workspace::discover(project_dir, &DiscoveryOptions::default()).await?;
 
     // Determine the default groups to include.
-    validate_dependency_groups(workspace.pyproject_toml(), &dev)?;
+    validate_dependency_groups(&VirtualProject::NonProject(workspace.clone()), &dev)?;
     let defaults = default_dependency_groups(workspace.pyproject_toml())?;
 
     // Find an interpreter for the project, unless `--frozen` and `--universal` are both set.
diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs
index 1841722d671b..56b61fb5dc47 100644
--- a/crates/uv/tests/it/sync.rs
+++ b/crates/uv/tests/it/sync.rs
@@ -502,6 +502,9 @@ fn sync_legacy_non_project_group() -> Result<()> {
         requires-python = ">=3.12"
         dependencies = ["iniconfig>1"]
 
+        [dependency-groups]
+        baz = ["typing-extensions"]
+
         [build-system]
         requires = ["setuptools>=42"]
         build-backend = "setuptools.build_meta"
@@ -559,6 +562,29 @@ fn sync_legacy_non_project_group() -> Result<()> {
      + typing-extensions==4.10.0
     "###);
 
+    uv_snapshot!(context.filters(), context.sync().arg("--group").arg("baz"), @r###"
+    success: true
+    exit_code: 0
+    ----- stdout -----
+
+    ----- stderr -----
+    Resolved 6 packages in [TIME]
+    Uninstalled 1 package in [TIME]
+    Installed 2 packages in [TIME]
+     + child==0.1.0 (from file://[TEMP_DIR]/child)
+     + iniconfig==2.0.0
+     - typing-extensions==4.10.0
+    "###);
+
+    uv_snapshot!(context.filters(), context.sync().arg("--group").arg("bop"), @r###"
+    success: false
+    exit_code: 2
+    ----- stdout -----
+
+    ----- stderr -----
+    error: Group `bop` is not defined in any project's `dependency-group` table
+    "###);
+
     Ok(())
 }