Execution

Click Extra bundles a few pre-configured options that control how a CLI runs: how long it takes (--time), how many parallel jobs it may use (--jobs), and what exit code it returns (-0/--zero-exit). Each publishes its resolved value on ctx.meta for downstream code to consume.

Timer

Click Extra can measure the execution time of a CLI via a dedicated --time/--no-time option.

Here how to use the standalone decorator:

from time import sleep
from click import command, echo, pass_context
from click_extra import timer_option

@command
@timer_option
def timer():
    sleep(0.2)
    echo("Hello world!")
$ timer --help
Usage: timer [OPTIONS]

Options:
  --time / --no-time  Measure and print elapsed execution time.
  --help              Show this message and exit.
$ timer --time
Hello world!
Execution time: 0.200 seconds.

You can get the timestamp of the CLI start from the context:

from click import command, echo, pass_context
from click_extra import timer_option

@command
@timer_option
@pass_context
def timer_command(ctx):
    start_time = ctx.meta["click_extra.start_time"]
    echo(f"Start timestamp: {start_time}")
$ timer --time
Start timestamp: 200.344603038
Execution time: 0.000 seconds.

Parallel jobs

A pre-configured --jobs option to control parallel execution. It accepts an integer, or one of two keywords: auto (the default: one fewer than the available logical CPU cores, leaving a core free for the main process and system tasks) and max (every available logical CPU core). A value of 0 disables parallelism and runs sequentially.

The option itself does not drive any concurrency: it only captures the user’s intent.

Important

The core count is the number of logical CPUs reported by Python’s os.cpu_count(): hardware threads, not physical cores. On a CPU with simultaneous multi-threading (Intel Hyper-Threading, AMD SMT) a 4-physical-core chip reports 8. This is deliberately the logical count, since subprocess- and I/O-bound work overlaps well across hardware threads. It can differ from the physical-core counts used elsewhere (psutil.cpu_count(logical=False), or pytest-xdist’s -n auto), so --jobs auto may pick a higher number than a physical-core heuristic would.

from click import command, echo, pass_context
from click_extra import jobs_option

@command
@jobs_option
@pass_context
def build(ctx):
    """Build the project."""
    jobs = ctx.meta["click_extra.jobs"]
    echo(f"Building with {jobs} parallel jobs.")
$ build --help
Usage: build [OPTIONS]

  Build the project.

Options:
  --jobs [auto|max|INTEGER]  Number of parallel jobs. Accepts an integer, 'auto'
                             (one fewer than the host's logical CPUs) or 'max'
                             (all logical CPUs). 0 runs sequentially.  [default:
                             auto]
  --help                     Show this message and exit.
$ build --jobs 4
warning: Requested 4 jobs exceeds available CPU cores (1).
Building with 4 parallel jobs.

The auto and max keywords resolve to a core count, keeping the same command portable across machines with different CPU counts:

$ build --jobs max
warning: '--jobs max' resolved to a single job: only 1 logical CPU is available, so execution will be sequential, not parallel.
Building with 1 parallel jobs.

Warning

A value of 0 disables parallelism: it is rounded up to 1 and a warning notes that execution will run sequentially. Negative values are likewise clamped to 1. When the count exceeds the available logical CPU cores, a warning is logged but the value is honored.

Warning

auto and max express a wish for parallelism, but on hosts with few logical CPUs they resolve to a single job and run sequentially: max on a single-core host, or auto on a one- or two-core host (it reserves one core). A warning is then logged, so the silent sequential fallback is not mistaken for parallel execution. An explicit --jobs 1 is treated as a deliberate sequential choice and stays silent.

Tip

The resolved (clamped, validated) job count is published on ctx.meta as JOBS for downstream code to consume. See the available keys table to read it from your own callbacks. It is also logged at info level alongside the host’s os.cpu_count(), so --verbosity INFO reveals how many workers a --jobs command will use.

Running jobs in parallel

run_jobs(func, items) maps func over items using the resolved --jobs count, so a command with @jobs_option parallelizes its work with no extra plumbing. It reads the worker count from the context (or an explicit jobs= override), runs sequentially when that count is 1 or there is a single item, and otherwise spreads the work across a thread pool. Results are yielded in submission order, like map.

from click import command, echo
from click_extra import jobs_option, run_jobs

@command
@jobs_option
def bake():
    """Bake several items in parallel."""
    items = ("apple", "banana", "cherry")
    for baked in run_jobs(str.upper, items):
        echo(f"Baked {baked}")
$ bake --jobs 2
warning: Requested 2 jobs exceeds available CPU cores (1).
Baked APPLE
Baked BANANA
Baked CHERRY

The pool is thread-based, which fits the I/O- and subprocess-bound work CLIs usually parallelize (each child releases the GIL). With a single worker the run stays lazy, so a caller can stop on the first result, for example to abort on the first failure.

Running lanes in parallel

Sometimes work cannot all run concurrently: a subset must be serialized relative to itself (a shared lock, a rate limit, one mailbox file read at a time, one package-manager backend) while still overlapping with unrelated subsets. run_lanes(func, lanes) groups items into lanes: a lane’s own items run serially and in order on a single worker, while distinct lanes run concurrently up to the resolved --jobs count. run_jobs is the degenerate case where every lane holds a single item.

from click import command, echo
from click_extra import jobs_option, run_lanes

@command
@jobs_option
def bake():
    """Bake several trays: items on a tray bake in order, trays bake in parallel."""
    trays = (("apple", "banana"), ("cherry",))
    for baked in run_lanes(str.upper, trays):
        echo(f"Baked {baked}")
$ bake --jobs 2
warning: Requested 2 jobs exceeds available CPU cores (1).
Baked APPLE
Baked BANANA
Baked CHERRY

Concurrency is sized by the number of lanes (one worker per lane), and results are yielded in lane-submission order. Because a lane runs entirely on one worker, a stateful resource bound to that lane (a per-lane cache, a connection) is touched by only one thread and needs no lock.

Resolving the job count

run_jobs and run_lanes decide their worker count internally, but a caller that must know it before fanning out (for example to pick a progress-rendering mode) can call resolve_jobs(ctx, count) directly. It returns the same number those helpers use: 1 (sequential) when there is no context, a single item, or --jobs 1, otherwise the resolved count capped at count. Passing serial_at_debug=True also collapses to sequential at DEBUG verbosity, where coherent per-worker log narration matters more than the speed-up; both helpers forward this flag.

Zero exit code

A pre-configured -0/--zero-exit option flag, following the convention popularized by linters and static analysers: they exit with a non-zero code whenever they report findings, so automation can gate on it. Setting this flag flips that behavior, so the CLI returns 0 as long as it ran to completion, reserving non-zero codes for actual execution failures.

The option itself does not alter the exit code: it only captures the user’s intent.

from click import command, echo, pass_context
from click_extra import zero_exit_option

@command
@zero_exit_option
@pass_context
def inspect(ctx):
    """Inspect a basket of fruits."""
    bruised = 2
    echo(f"Found {bruised} bruised fruits.")
    if bruised and not ctx.meta["click_extra.zero_exit"]:
        ctx.exit(1)
$ inspect --help
Usage: inspect [OPTIONS]

  Inspect a basket of fruits.

Options:
  -0, --zero-exit  Always exit with a status code of 0, even when problems are
                   found.
  --help           Show this message and exit.

By default the command reports a non-zero exit code when it finds problems:

$ inspect
Found 2 bruised fruits.

With --zero-exit (or its -0 shorthand) the command still reports its findings but always exits with 0:

$ inspect --zero-exit
Found 2 bruised fruits.

Tip

The resolved flag is published on ctx.meta as ZERO_EXIT for downstream code to consume. See the available keys table to read it from your own callbacks.

click_extra.execution API

        classDiagram
  ExtraOption <|-- JobsOption
  ExtraOption <|-- TimerOption
  ExtraOption <|-- ZeroExitOption
  ParamType <|-- JobCount
    

Options and primitives controlling how CLIs run.

Two altitudes live here. The higher one governs the CLI being authored: the pre-configured ExtraOption subclasses (JobsOption, TimerOption, ZeroExitOption) publish their resolved value on ctx.meta, and the fan-out primitives (run_jobs(), run_lanes()) parallelize work per the resolved --jobs count.

The lower one runs foreign CLIs in subprocesses, for tools that wrap other programs: run_cli() spawns one command, disclosing its invocation to the logger and streaming its output live, while install_interrupt_handler() and terminate_live_processes() make Ctrl+C abort in-flight children cleanly. args_cleanup() and format_cli_prompt() are the shared serialization and disclosure atoms both altitudes (and click_extra.testing) build upon.

click_extra.execution.TNestedArgs

Type for arbitrary nested CLI arguments.

Arguments can be str, pathlib.Path objects or None values.

alias of Iterable[str | Path | None | Iterable[TNestedArgs]]

click_extra.execution.CPU_COUNT = 1

Number of logical CPUs available, or None if undetermined.

This is os.cpu_count(), which counts logical processors (hardware threads). On a CPU with simultaneous multi-threading (Intel Hyper-Threading, AMD SMT) a 4-physical-core chip reports 8. It is therefore not a count of physical cores, and is usually larger than what physical-core tools report, such as psutil.cpu_count(logical=False) or pytest-xdist’s -n auto (which counts physical cores). Parallelism here is keyed on the logical count on purpose: subprocess- and I/O-bound work overlaps well across hardware threads.

click_extra.execution.DEFAULT_JOBS = 1

Default number of parallel jobs: one fewer than CPU_COUNT (logical CPUs).

Leaves one logical CPU free for the main process and system tasks. Falls back to 1 (sequential) when the count cannot be determined.

Caution

This resolves to 1 not only on single-core hosts but also on two-core hosts, since it reserves one core. There, the default silently runs sequentially. JobCount.convert() logs whenever a parallel-intent keyword collapses to a single job this way: as a warning for an explicit request, at info level for the option’s own default.

class click_extra.execution.JobCount[source]

Bases: ParamType

Parse a --jobs value: an integer or the auto/max keyword.

Resolves the symbolic keywords against the host’s logical CPU count (CPU_COUNT), counting hardware threads, not physical cores:

  • auto resolves to DEFAULT_JOBS (one fewer than the available logical CPUs), the same heuristic used as the option’s default.

  • max resolves to CPU_COUNT (every available logical CPU).

Any other token is parsed as an integer and left to JobsOption.validate_jobs() for clamping and range-checking. Resolving the keywords here keeps the value handed downstream a plain int, so consumers never have to know about the keywords.

name: str = 'jobs'

the descriptive name of this type

choices = ('auto', 'max')

Symbolic keywords accepted besides an integer count, in render order.

Exposed as choices so the help colorizer highlights them like click.Choice values: the keyword collector duck-types on this attribute (see the getattr(param.type, "choices", ...) branch in _HelpColorsMixin._collect_params). It is also the single source of truth reused by get_metavar() and convert(), so the metavar and the parser never drift apart.

get_metavar(param, ctx=None)[source]

Render [auto|max|INTEGER] (brackets included, as Choice does).

convert(value, param, ctx)[source]

Resolve a keyword to a logical-core count, else parse as an integer.

An already-resolved integer is returned untouched, so option defaults and re-validation can flow back through conversion unharmed. When a parallel-intent keyword (auto/max) resolves to a single job, the collapse is logged: the request reads as “use several cores”, but the host has too few logical CPUs, so execution is silently sequential. An explicit request (command line, environment variable, config file) logs a warning; the option’s own default only logs at info level, else every bare invocation on a 1-CPU host would emit a warning the user never asked for, polluting captured runner streams and the CLI output rendered in Sphinx docs.

Return type:

int

shell_complete(ctx, param, incomplete)[source]

Suggest the auto/max keywords; an integer count is free-form.

Completion proposes only the symbolic keywords, matched case-insensitively to mirror how convert() lower-cases its input. An integer has no finite set to enumerate, so none is offered, yet convert() still accepts one.

Return type:

list[CompletionItem]

class click_extra.execution.JobsOption(param_decls=None, default='auto', expose_value=False, show_default=True, type=<click_extra.execution.JobCount object>, help="Number of parallel jobs. Accepts an integer, 'auto' (one fewer than the host's logical CPUs) or 'max' (all logical CPUs). 0 runs sequentially.", **kwargs)[source]

Bases: ExtraOption

A pre-configured --jobs option to control parallel execution.

Accepts an integer or one of two keywords resolved by JobCount: auto (the default: one fewer than the available logical CPU cores, leaving a core free for the main process and system tasks) and max (every available logical CPU core). A value of 0 disables parallelism and runs sequentially.

The core count is the number of logical CPUs (hardware threads) reported by os.cpu_count(), not physical cores: see CPU_COUNT. On a host with too few logical CPUs, auto/max resolve to a single job and JobCount logs that execution will be sequential: as a warning when the keyword was requested explicitly, at info level when it came from the option’s own default.

The resolved value is stored as an int in ctx.meta[click_extra.context.JOBS].

Warning

JobsOption only resolves and publishes the job count: it does not drive any concurrency by itself. Pass it to run_jobs() (which reads the resolved ctx.meta[click_extra.context.JOBS] count), or read that value yourself and act on it.

validate_jobs(ctx, param, value)[source]

Validate the resolved job count and store it in context metadata.

JobCount has already resolved any auto/max keyword to an integer by the time this runs. A value of 0 disables parallelism: it is rounded up to 1 (sequential execution) with a warning. Negative values are likewise clamped to 1, and a count above the available cores is honored but warned about. The resolved count is then logged at info level next to the host’s logical CPU count (CPU_COUNT), so a CLI’s parallelism is visible under --verbosity INFO.

Return type:

None

click_extra.execution.resolve_jobs(ctx, count, *, serial_at_debug=False)[source]

Resolve how many worker threads to use for a batch of count items.

Returns the number of items to process in parallel; 1 means run sequentially in the calling thread. This is the policy shared by run_jobs() and run_lanes(), exposed on its own for callers that must know the resolved count before they fan out (for example to pick a progress-rendering mode). It collapses to sequential when:

  • there is no active CLI context (programmatic or test use),

  • a single item leaves nothing to parallelize, or

  • the resolved JobsOption count (ctx.meta[click_extra.context.JOBS]) is 1 or less.

Otherwise that count wins, capped at count: there is no point spinning up more workers than there are items.

Parameters:
  • ctx (Context | None) – the active Click context, read for the resolved --jobs count (and, with serial_at_debug, the verbosity). None forces sequential.

  • count (int) – how many items are about to be scheduled.

  • serial_at_debug (bool) – when set, also collapse to sequential at DEBUG verbosity, where coherent per-worker log narration matters more than the speed-up (interleaved threads would scramble it). Off by default.

Return type:

int

click_extra.execution.run_jobs(func, items, *, jobs=None, serial_at_debug=False)[source]

Run func over items, parallelized per the resolved --jobs count.

The worker count is taken from jobs when given, else resolved from the active command’s JobsOption value by resolve_jobs(), else 1. With a single worker (or at most one item) the items run sequentially and lazily, so a caller can stop early on the first result (for example to abort on the first failure); otherwise they run in a thread pool. Either way results are yielded in submission order, like map().

This is the single-task-per-item special case of run_lanes() (every item is its own lane). Reach for run_lanes() when some items must run serially relative to one another while others run concurrently.

The pool is thread-based, which suits the I/O- and subprocess-bound work CLI tools usually parallelize (each child releases the GIL). The count is a number of logical CPUs: see CPU_COUNT.

Parameters:
  • func (Callable[[TypeVar(T)], TypeVar(R)]) – Called once per item; its return value is yielded.

  • items (Iterable[TypeVar(T)]) – The work items. Materialized up front to size the pool.

  • jobs (int | None) – Override the worker count instead of reading it from the context. 1 or fewer forces sequential execution.

  • serial_at_debug (bool) – forwarded to resolve_jobs() when jobs is not given: collapse to sequential at DEBUG verbosity.

Return type:

Iterator[TypeVar(R)]

Returns:

An iterator over func’s results, in the order of items.

click_extra.execution.run_lanes(func, lanes, *, jobs=None, serial_at_debug=False)[source]

Run func over grouped items: serial within a lane, concurrent across.

Each lane is an iterable of items. func is mapped over every item, but a lane’s own items run serially and in order on a single worker, while distinct lanes run concurrently up to the resolved --jobs count. This is the right primitive when some work must be serialized relative to itself (a shared lock, a rate limit, one mailbox file, one package-manager backend) yet still overlap with unrelated work.

run_jobs() is the degenerate case where every lane holds a single item. Concurrency is sized by the number of lanes (one worker per lane), since a lane never splits across workers.

Results are yielded in lane-submission order, a lane’s items in order, like map(). With a single worker the run stays lazy (the caller can break early); otherwise every lane is submitted up front. A lane runs entirely on one worker, so a stateful resource bound to the lane (a per-lane cache, a connection) is touched by only that one thread and needs no lock.

Parameters:
  • func (Callable[[TypeVar(T)], TypeVar(R)]) – Called once per item; its return value is yielded.

  • lanes (Iterable[Iterable[TypeVar(T)]]) – The lanes, each an iterable of items. Materialized up front.

  • jobs (int | None) – Override the worker count instead of reading it from the context. 1 or fewer forces fully sequential execution.

  • serial_at_debug (bool) – forwarded to resolve_jobs() when jobs is not given: collapse to sequential at DEBUG verbosity.

Return type:

Iterator[TypeVar(R)]

Returns:

An iterator over func’s results, lane by lane in submission order.

class click_extra.execution.TimerOption(param_decls=None, default=False, expose_value=False, is_eager=True, help='Measure and print elapsed execution time.', **kwargs)[source]

Bases: ExtraOption

A pre-configured option that is adding a --time/--no-time flag to print elapsed time at the end of CLI execution.

The start time is made available in the context in ctx.meta[click_extra.context.START_TIME].

print_timer()[source]

Compute and print elapsed execution time.

Always prints, even when a sibling eager option (--version, --show-params, --show-config…) short-circuited the command body via ctx.exit(). That makes --time a usable probe for the cost of Click Extra’s own machinery (option parsing, config loading, eager callbacks), not just user command bodies.

Return type:

None

init_timer(ctx, param, value)[source]

Set up the execution-timer machinery for the current invocation.

Captures time.perf_counter() as the start time, stores it on ctx.meta under click_extra.context.START_TIME, and queues print_timer() as a context-close callback so the elapsed duration is printed even when a sibling eager option (--version, --show-params…) short-circuits the command body.

Renamed from register_timer_on_close to align with the init_<system> convention shared with init_formatter and init_sort.

Return type:

None

class click_extra.execution.ZeroExitOption(param_decls=None, default=False, expose_value=False, is_flag=True, help='Always exit with a status code of 0, even when problems are found.', **kwargs)[source]

Bases: ExtraOption

A pre-configured -0/--zero-exit option flag.

Follows the convention popularized by linters and static analysers, which exit with a non-zero code whenever they report findings so that automation can gate on it. Setting this flag flips that behavior: the CLI returns 0 as long as it ran to completion, reserving non-zero codes for actual execution failures.

The resolved value is stored in ctx.meta[click_extra.context.ZERO_EXIT], aligning with every other Click Extra option’s per-invocation context-meta storage pattern.

Warning

This option is a placeholder: it does not alter the CLI’s exit code by itself. Downstream code must read ctx.meta[click_extra.context.ZERO_EXIT] and act on it.

set_zero_exit(ctx, param, value)[source]

Store the resolved zero-exit flag on the context’s meta dict.

Read via click_extra.context.get(ctx, click_extra.context.ZERO_EXIT).

Return type:

None

click_extra.execution.PROMPT = '$ '

Prompt used to simulate the CLI execution.

Hint

Use ASCII characters to avoid issues with Windows terminals.

click_extra.execution.INDENT = '  '

Constants for rendering of CLI execution.

click_extra.execution.args_cleanup(*args)[source]

Flatten recursive iterables, remove all None, and cast each element to strings.

Helps serialize pathlib.Path and other objects.

It also allows for nested iterables and None values as CLI arguments for convenience. We just need to flatten and filters them out.

Return type:

tuple[str, ...]

click_extra.execution.highlight_bin_name(program)[source]

Style the binary’s own name inside program, leaving its directory plain.

/opt/homebrew/bin/mas renders with only mas in the active theme’s invoked_command style, so the part of the path the eye scans for stands out from the noise of its location. A bare name (no separator) is styled whole. Both POSIX and Windows separators are recognized, whichever comes last.

Return type:

str

click_extra.execution.format_cli_prompt(cmd_args, extra_env=None)[source]

Render the shell prompt simulating a CLI invocation, for logs and dry-runs.

Prefixes PROMPT to any extra_env assignments and the command line. Each token family is styled with the theme slot (get_current_theme()) it holds elsewhere in a CLI’s output, so the line reads like the help screens do:

  • the prompt sigil with bracket, the structural-token style;

  • each environment assignment as envvar name, plain =, default value;

  • the program’s binary name with invoked_command, its directory plain (see highlight_bin_name());

  • the -/-- flags with option; other arguments stay plain.

Useful to print a copy-pasteable command trace in debug logs, dry-runs and test output.

Return type:

str

click_extra.execution.terminate_live_processes()[source]

Send SIGTERM to every subprocess currently running through run_cli().

Called from the main thread’s SIGINT handler (see install_interrupt_handler()) so a concurrent fan-out aborts promptly: terminating the children unblocks the worker threads parked in run_cli(), letting the thread pool drain instead of hanging on a child that ignored the terminal’s process-group SIGINT.

Uses SIGTERM rather than SIGKILL so a child still gets to clean up, notably to restore terminal state a sudo password prompt may have altered. The registry is snapshotted under the lock, then signalled outside it, because run_cli() may be discarding its own entries from other threads at the same time.

Return type:

None

click_extra.execution.install_interrupt_handler(ctx)[source]

Make the first Ctrl+C terminate in-flight subprocesses, then abort as usual.

Installs a SIGINT handler for the duration of the CLI run that calls terminate_live_processes() before re-raising KeyboardInterrupt (exactly what Python’s default handler raises). The abort then proceeds normally, but a concurrent fan-out no longer hangs on surviving children. The previous handler is restored when ctx closes.

Must run in the main thread: signal.signal() refuses to install a handler from any other, so a non-main-thread caller (embedded use, some tests) is a no-op that keeps the default Ctrl+C behavior.

A signal handler is required here rather than a try/except KeyboardInterrupt around the fan-out: Python delivers Ctrl+C only to the main thread, so worker threads never see the interrupt, and the exception unwinds through the executor’s blocking shutdown(wait=True) teardown before any except in the caller could run. The children must be killed at signal-delivery time, ahead of that teardown.

Return type:

None

click_extra.execution.run_cli(args, *, extra_env=None, timeout=None, label=None, merge_streams=False, errors='replace', windows_creation_flags=0, command_level=20, output_level=10, log=None)[source]

Run a CLI in a subprocess, disclosing the call and streaming its output live.

A subprocess.run() work-alike for CLI-wrapping tools, with observability built in:

  • the invocation is logged before the spawn, as the copy-pasteable $ ENV=value command args line of format_cli_prompt(), so a user can reproduce by hand what the tool runs on their system;

  • each line of the child’s output is forwarded to the logger as it is produced (ANSI-stripped, tagged with label), instead of being held back until the child exits, so a long-running command narrates its progress live;

  • the child is registered in the live-process registry for the duration of the call, so terminate_live_processes() (wired to Ctrl+C by install_interrupt_handler()) can abort it.

Contract mirrored from subprocess.run():

  • returns a subprocess.CompletedProcess with the full captured stdout and stderr decoded as UTF-8;

  • raises subprocess.TimeoutExpired (with the partial capture attached) when the child, or the draining of its output, outlives timeout. The child is killed first, and its whole process tree on Windows (see _kill_windows_process_tree());

  • a KeyboardInterrupt mid-run kills the child, then propagates.

The child reads from subprocess.DEVNULL so it can never block on stdin, and never opens a console window on Windows.

Note

The pipes are opened in universal-newlines text mode, so a bare \r (a child redrawing a progress bar in place) terminates a line just like \n: each redraw is streamed as its own log line, and the captured text normalizes both to \n, exactly as subprocess.Popen.communicate() does.

Parameters:
  • args (str | Path | None | Iterable[str | Path | None | Iterable[Iterable[str | Path | None | Iterable[TNestedArgs]]]]) – the command line. Nested iterables are flattened, None values dropped, and every element (Path, versions, …) cast to a string; see args_cleanup().

  • extra_env (Mapping[str, str | None] | None) – environment variables forced over the inherited environment for this call (see env_copy()). They are part of the disclosed prompt line, since reproducing the call requires them.

  • timeout (float | None) – seconds before the child is killed. None waits forever.

  • label (str | None) – tag identifying this call on each streamed output line, for when several children interleave in one log. Carried as the record’s label attribute, which click_extra.logging.Formatter renders glued to the level name and styled like an invoked command (debug:mas: Warning: ...); a foreign formatter can read record.label itself. Applied to the output lines only, never the prompt line.

  • merge_streams (bool) – route the child’s stderr into stdout so the OS interleaves both in write order. The result’s stderr is then None, like a subprocess.run() call with stderr=STDOUT.

  • errors (str) – decoding error handler for the child’s output. The default "replace" swaps undecodable bytes for ; pass "backslashreplace" to keep them inspectable as escapes.

  • windows_creation_flags (int) – extra Windows process-creation flags, OR-ed with the always-on CREATE_NO_WINDOW. No-op off Windows.

  • command_level (int) – logging level of the invocation-disclosure line. Defaults to logging.INFO; lower it to logging.DEBUG for internal probes not worth narrating.

  • output_level (int) – logging level of the streamed output lines. Defaults to logging.DEBUG.

  • log (Logger | None) – destination logger. Defaults to the root logger, whose level the VerbosityOption family manages.

Return type:

CompletedProcess[str]