Fix: Empty output from HelpFormatter.write_usage for a program without arguments
Click bug #3360 produced empty write_usage output when args is empty. Fix PR #3433 adds an early return guard in formatting.py. A 4-line fix that illustrates why CLI formatting code needs explicit empty-input handling.

Key Takeaways — If your CLI formatting code assumes non-empty input, add a guard clause before layout logic. Test the simplest invocation (zero arguments) to catch silent output bugs. This pattern applies anywhere formatting code computes text width from user-supplied strings.
The Bug
Repo: pallets/click Issue: #3360 Status: PR-submitted PR: PR #3433
Click’s HelpFormatter.write_usage() produces empty output when a program has no arguments. Run a CLI tool with zero parameters (e.g., --version-only CLIs) and the usage line simply vanishes [1].
Root cause: write_usage assumes args is always non-empty and attempts to calculate text_width formatting with a zero-length string — producing no visible output [1].
Fix scope: 4 lines changed in src/click/formatting.py
The Fix
@@ -157,6 +157,10 @@ def write_usage(self, prog: str, args: str = "", prefix: str | None = None) -> N
usage_prefix = f"{prefix:>{self.current_indent}}{prog} "
text_width = self.width - self.current_indent
+ if not args:
+ self.write(f"{prefix:>{self.current_indent}}{prog}\n")
+ return
+
if text_width >= (term_len(usage_prefix) + 20):
# The arguments will fit to the right of the prefix.
The pattern is simple: guard clause before formatting logic. If args is empty, write the program name alone and return early. Don’t enter the formatting code that assumes non-empty input.
Why It Happens
The formatting code at src/click/formatting.py calculates text_width = self.width - self.current_indent and then checks whether arguments fit on the same line. With an empty args string, the width calculation doesn’t produce an error — it just produces nothing visible.
The Click documentation on custom formatting shows that write_usage expects at least a program name. The empty-args edge case was never handled because a CLI with zero parameters is rare — but valid. Tools that only expose --version or --help flags are common enough that this surfaces in practice.
What This Means for Your Code
Here’s a step-by-step checklist for hardening formatting code against empty-input bugs:
-
Use guard clauses for empty input — Before performing formatting or layout logic that assumes non-empty input, check the empty case and return early. This keeps the main code path clean and makes edge handling visible at the top of the function:
def format_output(items: list, width: int) -> str: if not items: return "" # guard clause — no items, no formatting # ... rest of formatting logic assumes items is non-empty -
Test the simplest invocation — This bug triggers on the most basic CLI invocation possible: running a tool with no arguments. Add test cases for minimal input. If your CLI formatting breaks with zero arguments, it breaks for every new user who just installed your tool.
-
Watch out for empty strings in layout code — Any function that computes text width, padding, or alignment from user-supplied strings should explicitly handle the empty case. The math doesn’t break — it just produces invisible output.
Quick reference: what to look for when reviewing formatting code
- Does the function assume at least one argument or item?
- Is there a guard clause for empty input before formatting logic?
- Are there test cases for the zero-argument invocation?
- Would empty output be silent or produce an error?
Transfer Potential
Medium — empty-input edge cases are universal across all languages, but the specific formatting pattern is CLI-tool specific. The guard clause pattern transfers anywhere formatting code assumes non-empty input.
Auto-generated from PR #3360. View all patches on GitHub.
[1]: See pallets/click#3360 and pallets/click#3433 for the issue and fix.