Repository: docker/compose
File: pkg/compose/hook.go (Go)

Code before:
)

func (s composeService) runHook(ctx context.Context, ctr container.Summary, service types.ServiceConfig, hook types.ServiceHook, listener api.ContainerEventListener) error {
	wOut := utils.GetWriter(func(line string) {
		listener(api.ContainerEvent{
			Type:      api.HookEventLog,
			Container: getContainerNameWithoutProject(ctr) + " ->",
			ID:        ctr.ID,
			Service:   service.Name,
			Line:      line,
		})
	})
	defer wOut.Close() //nolint:errcheck

	detached := listener == nil
	exec, err := s.apiClient().ContainerExecCreate(ctx, ctr.ID, container.ExecOptions{
		User:         hook.User,
		Privileged:   hook.Privileged,
		Env:          ToMobyEnv(hook.Environment),
		WorkingDir:   hook.WorkingDir,
		Cmd:          hook.Command,
		AttachStdout: !detached,
		AttachStderr: !detached,
	})
	if err != nil {
		// Ignore errors indicating that some containers were already stopped or removed.
		if cerrdefs.IsNotFound(err) || cerrdefs.IsConflict(err) && hook.Extensions["type"] == "pre-stop" {
			return nil
		}
		return err
	}

	if detached {
		return s.runWaitExec(ctx, exec.ID, service, listener)
	}

	height, width := s.stdout().GetTtySize()
	consoleSize := &[2]uint{height, width}
	attach, err := s.apiClient().ContainerExecAttach(ctx, exec.ID, container.ExecAttachOptions{
		Tty:         service.Tty,
		ConsoleSize: consoleSize,
	})
	if err != nil {
		return err
	}
	defer attach.Close()

	if service.Tty {
		_, err = io.Copy(wOut, attach.Reader)
	} else {
		_, err = stdcopy.StdCopy(wOut, wOut, attach.Reader)

Review comment (bug):
as `runHook` is only used in 2 places, and this applies to the `stopContainer` case, maybe we could check this error in `stopContainer` caller function and avoid a custom extension?
(this will also cover a race conditions where container is removed _after_ exec was created _but_ before  attach/start

Code after:

func (s composeService) runHook(ctx context.Context, ctr container.Summary, service types.ServiceConfig, hook types.ServiceHook, listener api.ContainerEventListener) error {
	wOut := utils.GetWriter(func(line string) {
		listener(api.ContainerEvent{
			Type:      api.HookEventLog,
			Container: getContainerNameWithoutProject(ctr) + " ->",
			ID:        ctr.ID,
			Service:   service.Name,
			Line:      line,
		})
	})
	defer wOut.Close() //nolint:errcheck

	detached := listener == nil
	exec, err := s.apiClient().ContainerExecCreate(ctx, ctr.ID, container.ExecOptions{
		User:         hook.User,
		Privileged:   hook.Privileged,
		Env:          ToMobyEnv(hook.Environment),
		WorkingDir:   hook.WorkingDir,
		Cmd:          hook.Command,
		AttachStdout: !detached,
		AttachStderr: !detached,
	})
	if err != nil {
		return err
	}

	if detached {
		return s.runWaitExec(ctx, exec.ID, service, listener)
	}

	height, width := s.stdout().GetTtySize()
	consoleSize := &[2]uint{height, width}
	attach, err := s.apiClient().ContainerExecAttach(ctx, exec.ID, container.ExecAttachOptions{
		Tty:         service.Tty,
		ConsoleSize: consoleSize,
	})
	if err != nil {
		return err
	}
	defer attach.Close()

	if service.Tty {
		_, err = io.Copy(wOut, attach.Reader)
	} else {
		_, err = stdcopy.StdCopy(wOut, wOut, attach.Reader)
	}
	if err != nil {
		return err
	}