From 19ac127cfe4cec2d99ff1021cad8f83f9976eece Mon Sep 17 00:00:00 2001 From: Tim Smith Date: Sun, 23 Aug 2026 12:50:21 -0700 Subject: [PATCH] Honor stdout and stderr false in Container#exec Container#exec resolved its stream options with stdout = options.delete(:stdout) || !detach stderr = options.delete(:stderr) || !detach so an explicit false fell through to `!detach` and came back as true. Neither stream could actually be switched off. The README documented `container.exec(['date'], stderr: false)` as a way to capture only stdout; it never did: stdout=["O\n"] stderr=["E\n"] # stderr: false Test for the key rather than its truthiness, keeping !detach as the default when the caller says nothing. Passing detach, or passing nothing, behaves exactly as before. --- lib/docker/container.rb | 7 +++++-- spec/docker/container_spec.rb | 24 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/lib/docker/container.rb b/lib/docker/container.rb index 3c0cc044..10ea8d98 100644 --- a/lib/docker/container.rb +++ b/lib/docker/container.rb @@ -60,8 +60,11 @@ def exec(command, options = {}, &block) detach = options.delete(:detach) || false user = options.delete(:user) stdin = options.delete(:stdin) - stdout = options.delete(:stdout) || !detach - stderr = options.delete(:stderr) || !detach + # `options.delete(:stdout) || !detach` turns an explicit false back into + # true, so these have to be tested for presence rather than truthiness. + # They default to the inverse of :detach when the caller says nothing. + stdout = options.key?(:stdout) ? options.delete(:stdout) : !detach + stderr = options.key?(:stderr) ? options.delete(:stderr) : !detach wait = options.delete(:wait) opts = { diff --git a/spec/docker/container_spec.rb b/spec/docker/container_spec.rb index d5c8a34f..f27b1c05 100644 --- a/spec/docker/container_spec.rb +++ b/spec/docker/container_spec.rb @@ -578,6 +578,30 @@ end end + # `stderr = options.delete(:stderr) || !detach` turns an explicit false back + # into true, so both stream options were impossible to switch off. + context 'when stderr is false' do + let(:output) { + subject.exec(['bash', '-c', 'echo out; echo err 1>&2'], stderr: false) + } + + it 'captures stdout but not stderr' do + expect(output[0]).to eq(["out\n"]) + expect(output[1]).to be_empty + end + end + + context 'when stdout is false' do + let(:output) { + subject.exec(['bash', '-c', 'echo out; echo err 1>&2'], stdout: false) + } + + it 'captures stderr but not stdout' do + expect(output[0]).to be_empty + expect(output[1]).to eq(["err\n"]) + end + end + context 'when passed a block' do it 'streams the stdout/stderr messages' do chunk = nil