fix(create, start): clear error when targeting a job by name

docker compose create <job> / start <job> failed with the raw
compose-go error "no such service: <name>", since project loading
rejects the job name before rejectScheduledJobs or any job-aware
logic ever runs -- unlike run, neither command knows how to detect
that a name is a declared job. The result gave no hint that jobs
exist or that run is the right command.

Jobs stay run-only by design; this only replaces the error message
when the target is a declared job, via a small helper (jobTargetErr)
shared by both commands, reloading unselected exactly like run's own
job-detection fallback. Any other "no such service" (a real typo)
keeps the original, precise error.

Signed-off-by: Nicolas De Loof <[email protected]>
This commit is contained in:
Nicolas De Loof
2026-09-21 16:35:20 +02:00
committed by Nicolas De loof
parent c8596c67fe
commit f07bec20cf
3 changed files with 44 additions and 3 deletions
+13 -3
View File
@@ -31,6 +31,7 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/docker/compose/v5/cmd/display"
"github.com/docker/compose/v5/pkg/api"
"github.com/docker/compose/v5/pkg/compose"
)
@@ -71,9 +72,18 @@ func createCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Bac
}
return nil
}),
RunE: p.WithServices(dockerCli, func(ctx context.Context, project *types.Project, services []string) error {
return runCreate(ctx, dockerCli, backendOptions, opts, buildOpts, project, services)
}),
RunE: func(cmd *cobra.Command, args []string) error {
err := p.WithServices(dockerCli, func(ctx context.Context, project *types.Project, services []string) error {
return runCreate(ctx, dockerCli, backendOptions, opts, buildOpts, project, services)
})(cmd, args)
if jobErr, replaced := jobTargetErr(cmd.Context(), dockerCli, p, args, err); replaced {
if display.Mode == display.ModeJSON {
return makeJSONError(jobErr)
}
return jobErr
}
return err
},
ValidArgsFunction: completeServiceNames(dockerCli, p),
}
flags := cmd.Flags()
+28
View File
@@ -322,6 +322,34 @@ func isNoSuchServiceErr(err error) bool {
return strings.Contains(err.Error(), "no such service")
}
// jobTargetErr recognizes an otherwise-raw compose-go selection error as
// targeting a declared job: unlike run, create and start don't materialize
// jobs — targeting one with either is run-only by design — so on a match it
// only returns a clearer error message (replaced=true), never falls
// through to acting on the job. names is the caller's original
// (unselected-by-profile) argument list, checked as-is against
// project.AllJobs() the same way run's own fallback resolves a job name.
func jobTargetErr(ctx context.Context, dockerCli command.Cli, p *ProjectOptions, names []string, err error) (jobErr error, replaced bool) {
if err == nil || len(names) == 0 || !isNoSuchServiceErr(err) {
return err, false
}
backend, berr := compose.NewComposeService(dockerCli)
if berr != nil {
return err, false
}
unselected, _, uerr := p.ToProject(ctx, dockerCli, backend, nil, skipUnsupportedAttributesWarning, composecli.WithoutEnvironmentResolution)
if uerr != nil {
return err, false
}
jobs := unselected.AllJobs()
for _, name := range names {
if _, ok := jobs[name]; ok {
return fmt.Errorf("job %q can only be triggered with \"docker compose run\"", name), true
}
}
return err, false
}
func runRun(ctx context.Context, backend api.Compose, project *types.Project, options runOptions, createOpts createOptions, buildOpts buildOptions, dockerCli command.Cli) error {
project, err := options.apply(project)
if err != nil {
+3
View File
@@ -54,6 +54,9 @@ func startCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Back
func runStart(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, opts startOptions, services []string) error {
project, name, err := opts.projectOrName(ctx, dockerCli, services...)
if err != nil {
if jobErr, replaced := jobTargetErr(ctx, dockerCli, opts.ProjectOptions, services, err); replaced {
return jobErr
}
return err
}
// a label-reconstructed project (no compose file) declares no jobs;