diff --git a/pkg/compose/up.go b/pkg/compose/up.go index b2bcb3f7db..1018968ebe 100644 --- a/pkg/compose/up.go +++ b/pkg/compose/up.go @@ -111,8 +111,20 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options globalCtx, cancel := context.WithCancel(ctx) defer cancel() + // startCtx keeps service startup isolated from parent cancellation, + // while still allowing the interactive detach action to interrupt + // startup/wait and return control to the user. + startCtx, cancelStart := context.WithCancel(context.WithoutCancel(ctx)) + defer cancelStart() + + var isDetached atomic.Bool + if navigationMenu != nil { - navigationMenu.EnableDetach(cancel) + navigationMenu.EnableDetach(func() { + isDetached.Store(true) + cancelStart() + cancel() + }) } var ( @@ -283,8 +295,12 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options }) // We use the parent context without cancellation as we manage sigterm to stop the stack - err = s.start(context.WithoutCancel(ctx), project.Name, options.Start, printer.HandleEvent) + err = s.start(startCtx, project.Name, options.Start, printer.HandleEvent) if err != nil && !isTerminated.Load() { // Ignore error if the process is terminated + if isDetached.Load() && errors.Is(err, context.Canceled) { + return nil + } + cancel() _ = eg.Wait() return err