From f07bec20cf408498ab338e98c4d0036a426cee83 Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Mon, 21 Sep 2026 11:55:52 +0200 Subject: [PATCH] fix(create, start): clear error when targeting a job by name docker compose create / start failed with the raw compose-go error "no such service: ", 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 --- cmd/compose/create.go | 16 +++++++++++++--- cmd/compose/run.go | 28 ++++++++++++++++++++++++++++ cmd/compose/start.go | 3 +++ 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/cmd/compose/create.go b/cmd/compose/create.go index 81f45e97b..211ec2bae 100644 --- a/cmd/compose/create.go +++ b/cmd/compose/create.go @@ -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() diff --git a/cmd/compose/run.go b/cmd/compose/run.go index aba0acea8..f3964f00b 100644 --- a/cmd/compose/run.go +++ b/cmd/compose/run.go @@ -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 { diff --git a/cmd/compose/start.go b/cmd/compose/start.go index 7ecde69e1..b1d06c950 100644 --- a/cmd/compose/start.go +++ b/cmd/compose/start.go @@ -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;