Summary
Before Click parses arguments, run_app() decides whether to buffer stdout for JSON-envelope wrapping using a naive scan for the literal token --json in argv — which diverges from Click's actual parse result.
Details
lib/python/base_cli/app.py:2971 (_json_requested) just checks whether a literal "--json" token appears anywhere in args, consumed at :2859 and :2872.
- The correct value is only known later, via
_capture_standard_options (:306).
- This diverges from Click's real parsing whenever
--json is set through anything other than an exact literal token: a Click default_map (a standard Click feature), or a combined short-flag form like -xj (meaning -x -j, standard Click flag-combining). Both were reproduced directly.
- The same heuristic-scanning weakness also exists in
_leading_output_flags/_lifecycle_flag_declarations (:3121/:3089) for early debug/quiet detection — lower impact there since it only affects diagnostic verbosity before real option parsing completes.
Impact
When JSON is actually requested via default_map or a combined short flag, the command's own print() output goes to raw stdout (never captured) while run_app() still appends a JSON envelope afterward — producing stdout that is prose followed by JSON. This directly violates the documented contract in docs/json-contracts.md ("exactly one success or error envelope on stdout... cannot introduce prose... as a second stdout record") and breaks any automation parsing stdout as JSON.
Suggested fix
Don't pre-decide based on raw argv; always capture stdout into a buffer for commands whose LifecycleOptions.json is configured (the cost is negligible), and make the final "wrap or not" decision solely from the Click-parsed value already captured in _capture_standard_options.
Summary
Before Click parses arguments,
run_app()decides whether to buffer stdout for JSON-envelope wrapping using a naive scan for the literal token--jsonin argv — which diverges from Click's actual parse result.Details
lib/python/base_cli/app.py:2971(_json_requested) just checks whether a literal"--json"token appears anywhere inargs, consumed at:2859and:2872._capture_standard_options(:306).--jsonis set through anything other than an exact literal token: a Clickdefault_map(a standard Click feature), or a combined short-flag form like-xj(meaning-x -j, standard Click flag-combining). Both were reproduced directly._leading_output_flags/_lifecycle_flag_declarations(:3121/:3089) for early debug/quiet detection — lower impact there since it only affects diagnostic verbosity before real option parsing completes.Impact
When JSON is actually requested via
default_mapor a combined short flag, the command's ownprint()output goes to raw stdout (never captured) whilerun_app()still appends a JSON envelope afterward — producing stdout that is prose followed by JSON. This directly violates the documented contract indocs/json-contracts.md("exactly one success or error envelope on stdout... cannot introduce prose... as a second stdout record") and breaks any automation parsing stdout as JSON.Suggested fix
Don't pre-decide based on raw argv; always capture stdout into a buffer for commands whose
LifecycleOptions.jsonis configured (the cost is negligible), and make the final "wrap or not" decision solely from the Click-parsed value already captured in_capture_standard_options.