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:

  1. 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
  2. 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.

  3. 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.