You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
An attached command with an optional lifecycle JSON flag executes option callbacks and their close hooks twice on a normal human invocation. Merely offering JSON output changes native Click behavior. Callable defaults, custom parameter types, lazy resolvers, and resource hooks are also exposed to the extra resilient parse. This can repeat application work before the real command starts.
Verified evidence
Reviewed on 2026-09-12 at 7faf347a5b90969e169c88f3ac18e7202d142dd0; the local checkout matches GitHub main.
Create a click.command(name="probe") with click.option("--payload", default="hi", callback=callback). Have the callback append ctx.resilient_parsing to a list and register a ctx.call_on_close hook. Attach it to App(name="probe", lifecycle_options=LifecycleOptions(json=LifecycleOption("--json"))) and call run_app(command, []).
Observed callback flags: [True, False]; both close hooks execute. With run_app(command, ["--json"]), the callback executes only once ([False]). _click_lifecycle_value() calls make_context(..., resilient_parsing=True) and closes those contexts before actual invocation. Resilient parsing is not a side-effect-free parse. The command body itself executes once.
Problem
An attached command with an optional lifecycle JSON flag executes option callbacks and their close hooks twice on a normal human invocation. Merely offering JSON output changes native Click behavior. Callable defaults, custom parameter types, lazy resolvers, and resource hooks are also exposed to the extra resilient parse. This can repeat application work before the real command starts.
Verified evidence
Reviewed on 2026-09-12 at
7faf347a5b90969e169c88f3ac18e7202d142dd0; the local checkout matches GitHub main.Create a
click.command(name="probe")withclick.option("--payload", default="hi", callback=callback). Have the callback appendctx.resilient_parsingto a list and register actx.call_on_closehook. Attach it toApp(name="probe", lifecycle_options=LifecycleOptions(json=LifecycleOption("--json")))and callrun_app(command, []).Observed callback flags:
[True, False]; both close hooks execute. Withrun_app(command, ["--json"]), the callback executes only once ([False])._click_lifecycle_value()callsmake_context(..., resilient_parsing=True)and closes those contexts before actual invocation. Resilient parsing is not a side-effect-free parse. The command body itself executes once.Sources:
Acceptance criteria
Related work
Follow-up to closed #304 and #268; the previously tested activation cases pass, but callback multiplicity was not covered.
Project fields