From b5630f730559cc0dc564808ab3d0427eeeab5f11 Mon Sep 17 00:00:00 2001 From: Dean Chen <862469039@qq.com> Date: Sat, 5 Sep 2026 00:05:55 +0500 Subject: [PATCH] fix(exec): don't steal stdin when stdout is not a TTY `docker compose exec ... | less` keeps stdin by default, so compose and less both grab the keyboard. Default -i to the same TTY check already used for -T. Fixes #13129 Signed-off-by: Dean Chen <862469039@qq.com> --- cmd/compose/exec.go | 5 +++-- cmd/compose/exec_test.go | 44 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 cmd/compose/exec_test.go diff --git a/cmd/compose/exec.go b/cmd/compose/exec.go index f548730dc06..8ae135b6138 100644 --- a/cmd/compose/exec.go +++ b/cmd/compose/exec.go @@ -83,10 +83,11 @@ func execCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backe runCmd.Flags().IntVar(&opts.index, "index", 0, "Index of the container if service has multiple replicas") runCmd.Flags().BoolVarP(&opts.privileged, "privileged", "", false, "Give extended privileges to the process") runCmd.Flags().StringVarP(&opts.user, "user", "u", "", "Run the command as this user") - runCmd.Flags().BoolVarP(&opts.noTty, "no-tty", "T", !dockerCli.Out().IsTerminal(), "Disable pseudo-TTY allocation. By default 'docker compose exec' allocates a TTY.") + isTTY := dockerCli.Out().IsTerminal() + runCmd.Flags().BoolVarP(&opts.noTty, "no-tty", "T", !isTTY, "Disable pseudo-TTY allocation. By default 'docker compose exec' allocates a TTY.") runCmd.Flags().StringVarP(&opts.workingDir, "workdir", "w", "", "Path to workdir directory for this command") - runCmd.Flags().BoolVarP(&opts.interactive, "interactive", "i", true, "Keep STDIN open even if not attached") + runCmd.Flags().BoolVarP(&opts.interactive, "interactive", "i", isTTY, "Keep STDIN open even if not attached") runCmd.Flags().MarkHidden("interactive") //nolint:errcheck runCmd.Flags().BoolP("tty", "t", true, "Allocate a pseudo-TTY") runCmd.Flags().MarkHidden("tty") //nolint:errcheck diff --git a/cmd/compose/exec_test.go b/cmd/compose/exec_test.go new file mode 100644 index 00000000000..6241c725090 --- /dev/null +++ b/cmd/compose/exec_test.go @@ -0,0 +1,44 @@ +/* + Copyright 2020 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package compose + +import ( + "os" + "testing" + + "github.com/docker/cli/cli/streams" + "go.uber.org/mock/gomock" + "gotest.tools/v3/assert" + + "github.com/docker/compose/v5/pkg/mocks" +) + +func TestExecInteractiveDefaultOffWhenStdoutNotTTY(t *testing.T) { + r, w, err := os.Pipe() + assert.NilError(t, err) + t.Cleanup(func() { + _ = r.Close() + _ = w.Close() + }) + + cli := mocks.NewMockCli(gomock.NewController(t)) + cli.EXPECT().Out().Return(streams.NewOut(w)).AnyTimes() + + cmd := execCommand(&ProjectOptions{}, cli, &BackendOptions{}) + assert.Equal(t, cmd.Flags().Lookup("interactive").DefValue, "false") + assert.Equal(t, cmd.Flags().Lookup("no-tty").DefValue, "true") +}