A child command can write to standard error and still succeed. It can also fail without writing an error message. In Python automation, do not use a nonempty stderr string as your universal failure condition. Capture the exit status and output streams separately, interpret the command’s documented status convention, and treat diagnostics as supporting evidence rather than a substitute for the result.
Recognise three different pieces of evidence
The official Python subprocess documentation describes CompletedProcess.returncode, stdout and stderr separately. With capture_output=True, both output streams are collected. With text=True, captured output is returned as text rather than bytes. A program is free to send warnings or progress messages to stderr; the stream name does not mechanically establish whether its task succeeded.
For a command whose contract uses zero for success and nonzero for failure, classify its reported outcome from returncode. Some tools have accepted nonzero outcomes, such as a search with no matches, so check that contract before deploying a generic wrapper. Conversely, exit zero is not independent proof that an export contains the required records or that a remote change was processed. Validate those postconditions separately.
Test warning-only success and silent failure
Save the following as stream_evidence.py and run it with Python 3. It launches only fixed, harmless Python snippets using the same interpreter as the parent. No external service, credentials or production files are involved. The two cases deliberately contradict the assumption that stderr text is a failure flag.
import subprocess
import sys
cases = [
(
"warning-only success",
"import sys; print('demo warning', file=sys.stderr); sys.exit(0)",
0,
True,
),
(
"silent failure",
"import sys; sys.exit(7)",
7,
False,
),
]
for name, child_code, expected_code, expected_stderr in cases:
result = subprocess.run(
[sys.executable, "-c", child_code],
capture_output=True,
text=True,
stdin=subprocess.DEVNULL,
timeout=10,
check=False,
)
assert result.returncode == expected_code
assert bool(result.stderr) == expected_stderr
outcome = "success" if result.returncode == 0 else "failure"
print(f"{name}: {outcome}, stderr_present={bool(result.stderr)}")
The first case should print success with stderr_present=True; the second should print failure with stderr_present=False. Notice that this is a test harness: if its assertions pass, the harness itself finishes normally even though one demonstration child failed. A real operational wrapper must deliberately propagate its chosen failure outcome to its caller. See making Python automation fail when a child command fails for that separate control-flow task.
Keep diagnostics useful without leaking them
Record the command identity, interpreted result, original return code and whether diagnostics were present. Do not automatically publish raw stderr to a shared chat or alert channel. Real tools can expose tokens, customer information, file paths or connection details in either stream. Decide who may see the evidence, redact sensitive fields and set a retention policy.
Leave the streams separate when their distinction matters. Combining stderr into stdout loses the original channel information, and a combined text stream does not necessarily provide a complete timestamped ordering of events. If a later parser expects machine-readable stdout, diagnostic text merged into that stream can break parsing even when the command itself succeeded.
Handle execution failures separately from command results
A missing executable or an operating-system launch error may raise OSError before there is any completed child result. A timeout raises TimeoutExpired, not an ordinary completed return code. Build explicit paths for those conditions rather than interpreting an absent stderr string as success. With check=True, a nonzero result raises CalledProcessError; captured diagnostics remain evidence available through the exception, not a different success rule.
Captured output is buffered in memory. The example is appropriate for small, bounded messages, not unlimited log output from a long-running service. For larger workloads, design controlled file logging or incremental processing with resource limits. A timeout, exception or failure message does not roll back a real command’s partial changes; reconcile completed work before retrying.
Alerting checklist and FAQ
- Test success with stderr, failure without stderr, and ordinary clean success.
- Confirm the command’s accepted return codes before classification.
- Keep launch errors and timeouts distinct from completed commands.
- Verify domain-specific postconditions and the wrapper’s recorded outcome.
- Review captured output for sensitive information before disclosure.
Should warnings be ignored? No. A warning may deserve investigation even when the command reports success. Give it an appropriate diagnostic severity rather than automatically declaring task failure. Does empty stderr prove health? No. The silent-failure fixture demonstrates why message absence cannot establish a successful result.
Safety: This tutorial is for educational purposes. Test carefully before using automation in production. The site owner is not responsible for misuse, damage, data loss or production issues.



