CLI¶
The taskq console entry point (Typer).
The directive below renders taskq.cli as a Python module: the typer.Typer
sub-apps and the command callbacks behind each taskq subcommand. That is the
implementation, not the command surface - flags, defaults, exit codes and
example output are documented in the
CLI reference, which is the operator-facing document for
every subcommand.
Module reference¶
cli ¶
taskq CLI entry point.
Usage::
taskq migrate status
taskq migrate up [--phase pre|post] [--target VERSION] [--max-steps N] [--ddl-lock-timeout SECS]
taskq migrate disable-hypertables
taskq worker --actors myapp.actors:registry
taskq job show JOB_ID
taskq job cancel JOB_ID [--reason TEXT]
taskq job retry JOB_ID
taskq job cancel-where [--queue NAME] [--status S]... [--dry-run]
taskq insights [wait|balance|drain|cron|all] [--window 1h|6h|24h|7d] [--actor A] [--queue NAME]
The console script puts the current working directory on sys.path
(see :func:main), so module:attr options resolve application modules
from the directory the operator ran the command in.
app
module-attribute
¶
app = typer.Typer(
name="taskq",
no_args_is_help=True,
help="TaskQ, async Postgres-backed background jobs.",
)
migrate_app
module-attribute
¶
health_app
module-attribute
¶
workgroup_app
module-attribute
¶
workgroup_app = typer.Typer(
no_args_is_help=True,
help="Manage a multi-worker process group (supervisor).",
)
actor_config_app
module-attribute
¶
actor_config_app = typer.Typer(
no_args_is_help=True,
help="Inspect and tune stored actor_config capacity fields on a live deployment.",
)
queues_app
module-attribute
¶
queues_app = typer.Typer(
no_args_is_help=True,
help="Inspect and configure queue dispatch mode and per-queue concurrency caps.",
)
queue_app
module-attribute
¶
job_app
module-attribute
¶
job_app = typer.Typer(
no_args_is_help=True,
help="Inspect individual jobs and operate on them (cancel, retry, bulk cancel).",
)
worker ¶
worker(
actors: str = typer.Option(
...,
"--actors",
help="Module:attr reference to the actor registry (e.g. myapp.actors:registry). Resolves at startup to a Mapping[str, ActorRef] or Iterable[ActorRef].",
),
force_update_actor_config: bool = typer.Option(
False,
"--force-update-actor-config",
help="Allow sync_actor_config to overwrite a stored actor_config row whose metadata differs from the registered value. Use for one deploy to deliberately adopt a code-side metadata change, then unset. The queue assignment is unaffected, boots never rewrite it; move an actor with `taskq actor-config move-queue`. Capacity fields (max_concurrent / max_pending / result_ttl) are likewise unaffected, use `taskq actor-config set` for those. Equivalent to env var TASKQ_FORCE_UPDATE_ACTOR_CONFIG=true.",
),
queues: list[str] | None = typer.Option(
None,
"--queues",
help="Queue names to consume from (repeat the flag once per queue). Overrides TASKQ_QUEUES.",
),
max_concurrency: int | None = typer.Option(
None,
"--max-concurrency",
help="Upper bound on concurrent jobs. Overrides TASKQ_MAX_CONCURRENCY.",
),
poll_interval: float | None = typer.Option(
None,
"--poll-interval",
help="Producer loop fallback polling cadence in seconds. Overrides TASKQ_POLL_INTERVAL.",
),
worker_group: str | None = typer.Option(
None,
"--worker-group",
help="Consumer group name for observability spans. Overrides TASKQ_WORKER_GROUP.",
),
worker_label: str | None = typer.Option(
None,
"--worker-label",
help="Human-readable label stored in the workers table for correlation with workgroup supervisors and external monitoring.",
),
workgroup_instance: str | None = typer.Option(
None,
"--workgroup-instance",
help="UUIDv7 identifying the workgroup orchestrator that launched this worker. Used for cross-process correlation and health checking.",
),
health_socket_path: str | None = typer.Option(
None,
"--health-socket-path",
help="Unix socket path for the health server. Overrides TASKQ_HEALTH_SOCKET_PATH. Use unique paths when running multiple workers on the same host.",
),
until_idle: bool = typer.Option(
False,
"--until-idle",
help="Run until all subscribed queues are drained, then exit. Exit 0 if all jobs succeeded, 3 if any failed, 4 if idle-max-runtime was exceeded. Incompatible with cron-driven workloads.",
),
idle_settle_window: float | None = typer.Option(
None,
"--idle-settle-window",
help="Seconds to wait after queues appear empty before declaring drained. Overrides TASKQ_IDLE_SETTLE_WINDOW. Default 2.0. Only used with --until-idle.",
),
idle_poll_interval: float | None = typer.Option(
None,
"--idle-poll-interval",
help="How often to check queue depth. Overrides TASKQ_IDLE_POLL_INTERVAL. Default 1.0. Only used with --until-idle.",
),
idle_max_runtime: float | None = typer.Option(
None,
"--idle-max-runtime",
help="Maximum wall-clock seconds before forcing exit (code 4). Overrides TASKQ_IDLE_MAX_RUNTIME. Only used with --until-idle.",
),
pg_credential_provider: str | None = typer.Option(
None,
"--pg-credential-provider",
help=f"Module:attr reference to a PgCredentialProvider (e.g. {_PROVIDER_EXAMPLE}), an instance, a zero-arg factory returning one, or the provider class. Every Postgres pool and dedicated connection is then built through it, so SIGHUP / TASKQ_RELOAD_INTERVAL rotate real credentials. Overrides TASKQ_PG_CREDENTIAL_PROVIDER (inherited by workgroup-supervised workers) via dotenvmodel.",
),
redis_credential_provider: str | None = typer.Option(
None,
"--redis-credential-provider",
help="Module:attr reference to a RedisCredentialProvider, in the same shapes as --pg-credential-provider. Requires TASKQ_REDIS_URL. Overrides TASKQ_REDIS_CREDENTIAL_PROVIDER via dotenvmodel.",
),
) -> None
Start a TaskQ worker consuming from the given actor registry.
Source code in src/taskq/cli.py
440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 | |
dev_watch ¶
dev_watch(
actors: Annotated[
str,
Argument(help="Import path: dotted.module:attr"),
],
watch: Annotated[
list[str] | None,
Option(
--watch,
help="Path to watch (repeatable). Default: cwd.",
),
] = None,
grace_period: Annotated[
int,
Option(
--grace - period,
min=0,
help="Seconds before SIGKILL. Default: 5.",
),
] = 5,
) -> None
Run a worker in dev mode with auto-reload on file changes.
Source code in src/taskq/cli.py
migrate_status ¶
migrate_status(
pg_credential_provider: str | None = typer.Option(
None,
"--pg-credential-provider",
help=f"Module:attr reference to a PgCredentialProvider (e.g. {_PROVIDER_EXAMPLE}). The connection is opened through it instead of the DSN's static password. Overrides TASKQ_PG_CREDENTIAL_PROVIDER.",
),
) -> None
Show applied and pending migrations.
Source code in src/taskq/cli.py
migrate_up ¶
migrate_up(
phase: Phase | None = typer.Option(
None, "--phase", help="Restrict to 'pre' or 'post'."
),
target: str | None = typer.Option(
None,
"--target",
help="Stop after this version (inclusive). E.g. 01.00.00_01",
),
max_steps: int | None = typer.Option(
None, "--max-steps", help="Cap number of applies."
),
ddl_lock_timeout: float = typer.Option(
migrate_mod.DEFAULT_MIGRATION_DDL_LOCK_TIMEOUT,
"--ddl-lock-timeout",
min=0.0,
help="Seconds a transactional migration waits for a table lock before it fails and rolls back (SET LOCAL lock_timeout). 0 waits indefinitely, parking every statement on the table behind the queued DDL.",
),
allow_checksum_drift: bool = typer.Option(
False,
"--allow-checksum-drift",
help="Proceed past a drifted checksum ledger: an APPLIED migration's ledger checksum differs from the bundled file, which refuses the run by default. Verify the edit is safe first; the drift is still logged as a warning on every run and the ledger keeps the stored checksum.",
),
pg_credential_provider: str | None = typer.Option(
None,
"--pg-credential-provider",
help=f"Module:attr reference to a PgCredentialProvider (e.g. {_PROVIDER_EXAMPLE}). The connection is opened through it instead of the DSN's static password. Overrides TASKQ_PG_CREDENTIAL_PROVIDER.",
),
) -> None
Apply pending migrations.
Source code in src/taskq/cli.py
migrate_disable_hypertables ¶
migrate_disable_hypertables(
pg_credential_provider: str | None = typer.Option(
None,
"--pg-credential-provider",
help=f"Module:attr reference to a PgCredentialProvider (e.g. {_PROVIDER_EXAMPLE}). The connection is opened through it instead of the DSN's static password. Overrides TASKQ_PG_CREDENTIAL_PROVIDER.",
),
) -> None
Convert the TimescaleDB hypertables back to plain vanilla tables.
The disable mirror of the migrate up deploy step's conversion: every
registered retention/compression policy is removed, and per hypertable the
rows return into a table whose shape is the bundled migrations' own output
(count- and twin-verified at every hand-off — see
docs/guides/timescaledb.md). Flip TASKQ_TIMESCALEDB_HYPERTABLES
off FIRST: with the flag still true this refuses loudly (the library-level
gate is a zero-statement no-op; the CLI does not let a mistyped invocation
look like a completed disable). A crashed run converges on the re-run.
Source code in src/taskq/cli.py
actor_config_list ¶
actor_config_get ¶
Show the stored actor_config row for one actor.
Source code in src/taskq/cli.py
actor_config_set ¶
actor_config_set(
actor: Annotated[str, Argument(help="Actor name.")],
max_concurrent: Annotated[
int | None,
Option(
--max - concurrent,
min=0,
help="New fleet-wide concurrency cap. Takes effect on the next dispatch cycle (no worker restart), the dispatch query re-reads this column every cycle.",
),
] = None,
clear_max_concurrent: Annotated[
bool,
Option(
--clear - max - concurrent,
help="Set max_concurrent back to unlimited.",
),
] = False,
max_pending: Annotated[
int | None,
Option(
--max - pending,
min=0,
help="New queue-depth backpressure cap. Takes effect within seconds on every enqueue-side process (bounded by each client's capacity-cache TTL, default 5s) , no redeploy, no worker restart.",
),
] = None,
clear_max_pending: Annotated[
bool,
Option(
--clear - max - pending,
help="Clear the stored override; enforcement reverts to the @actor(max_pending=...) literal.",
),
] = False,
result_ttl: Annotated[
float | None,
Option(
--result - ttl,
min=0,
help="New result TTL in seconds. Takes effect for jobs completing after this change (no worker restart), the terminal-write UPDATE re-reads this column for every job.",
),
] = None,
clear_result_ttl: Annotated[
bool,
Option(
--clear - result - ttl,
help="Set result_ttl back to unset.",
),
] = False,
) -> None
Update capacity fields on an existing actor_config row.
Only flags actually passed are changed. An actor must already have a stored row (created by a worker startup that registered it) before its capacity can be tuned here.
All three fields are live: --max-concurrent is re-read by the
dispatch query every cycle and --result-ttl by the terminal-write
path on every job completion (both immediate); --max-pending is
re-read by every enqueue-side process through a TTL-bounded cache
(default 5s staleness). No redeploy and no worker restart for any of
them. Use taskq actor-config diff to see the stored value, the
code literal, and which one the engine currently enforces.
Source code in src/taskq/cli.py
1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 | |
actor_config_deregister ¶
actor_config_deregister(
actor: Annotated[
str, Argument(help="Actor name to deregister.")
],
force: Annotated[
bool,
Option(
--force,
help="Cancel pending/scheduled jobs and disable enabled cron schedules instead of refusing. Running jobs still block deregistration.",
),
] = False,
purge_queue: Annotated[
bool,
Option(
--purge - queue,
help="Also delete the orphaned queues row if no other actor_config references the same queue.",
),
] = False,
) -> None
Deregister an actor: delete its actor_config row with safety checks.
By default refuses if non-terminal jobs or enabled cron schedules reference the actor. Use --force to cancel pending/scheduled jobs and disable schedules. Running jobs always block (force or not). Use --purge-queue to also delete the queues row if no other actor uses it.
Exit codes: 0 success, 2 refusal (active jobs/schedules or invalid schema), 3 not found.
Source code in src/taskq/cli.py
actor_config_move_queue ¶
actor_config_move_queue(
actor: Annotated[
str, Argument(help="Actor name to move.")
],
new_queue: Annotated[
str, Argument(help="Target queue name.")
],
) -> None
Move an actor to a different queue in ONE operator action.
Rewrites the stored queue assignment, carries the old queue's mode and max_concurrent to the target when the target has no row of its own, and moves the actor's pending/scheduled backlog onto the target (bounded batches, then one final transaction for the flip) so old-queue strays drain through the target's consumers. Running jobs finish on the workers that claimed them; any that re-pend instead (failure retry, crash reclaim, operator retry) keep their old queue label as an audit trail but are ROUTED at dispatch by the actor's current assignment , the tail drains through the target queue's consumers, never stranded on the retired source queue. Cron fires follow the moved assignment from the flip on.
Workers boot on either side of the matching code deploy, in any order:
a stale @actor(queue=...) literal logs actor-config-queue-override
and adopts the stored assignment instead of refusing boot.
Exit codes: 0 moved, 2 refusal (invalid queue name, the actor is already on that queue, or the assignment changed concurrently), 3 no stored row.
Source code in src/taskq/cli.py
queue_migrate ¶
queue_migrate(
actor: Annotated[
str, Argument(help="Actor name to move.")
],
to: Annotated[
str,
Option(
--to,
help="Target queue name. Required, the target is never defaulted.",
),
],
) -> None
Move an actor onto a different queue, and report the residual.
Applies the coordinated writes of a move, the stored assignment and
the target queues row carrying the source's mode and cap, in one
transaction, so a failure leaves the deployment exactly where it
started rather than half-moved. The actor's pending/scheduled backlog
is rewritten onto the target first, as bounded committed batches.
The target is named by --to rather than positionally: the two
arguments of a move are an actor and a queue, both plain strings, and
two bare positionals are the shape an operator transposes under
pressure, with the consequence that the backlog drains onto a queue
that was never the target.
Reports how many pending jobs still carry the source queue label. Producers still running the old literal keep placing jobs there, and those stay served by the source queue's consumers, so that count is what tells the operator when the retired queue can stop being consumed.
Exit codes: 0 moved, 2 refusal (invalid queue name, the actor is already on that queue, the assignment changed concurrently, or a drain batch timed out and the move is re-runnable), 3 no stored row.
Source code in src/taskq/cli.py
actor_config_diff ¶
actor_config_diff(
actors: Annotated[
str,
Option(
--actors,
help="Module:attr reference to the actor registry (e.g. myapp.actors:registry). Stored rows are compared against these code literals.",
),
],
) -> None
Diff stored actor_config rows against the code literals in a registry.
Per actor and field, shows the @actor(...) literal, the stored value,
and the value the engine actually enforces right now ("effective").
Reach for this when debugging "why is my change not taking effect":
a capacity literal that differs from the stored row is IGNORED at
runtime, the stored value wins; tune it with taskq actor-config
set, and a queue mismatch means the two routing halves disagree
(boot adopts the stored queue and cron fires follow it while producers
enqueue by their own literal); reconcile with taskq actor-config
move-queue. A metadata mismatch still refuses the next worker
startup with ActorConfigDriftList.
Exit codes: 0 no gate-failing drift; 1 at least one actor fails the gate, a registry actor with no stored row (it does not dispatch until one is seeded), a queue assignment mismatch (producer literals and cron routing disagree), or a metadata mismatch (startup-blocking). Capacity-only differences never affect the exit code: stored capacity is operator-owned by design, so they are reportable drift, not gate-failing drift.
Source code in src/taskq/cli.py
doctor ¶
doctor(
actors: Annotated[
str,
Option(
--actors,
help="Module:attr reference to the actor registry (e.g. myapp.actors:registry). Stored rows are read against these registered actors.",
),
],
platform_grace_seconds: Annotated[
float | None,
Option(
--platform - grace - seconds,
help="The orchestrator's stop grace for this deployment (Kubernetes terminationGracePeriodSeconds, ACA/ECS stop timeout, compose stop_grace_period, systemd TimeoutStopSec). When given, doctor compares it against the worker's modelled worst-case shutdown and reports the shortfall: a platform grace below it SIGKILLs the worker mid-teardown and in-flight work re-runs.",
),
] = None,
) -> None
Report capacity and configuration conditions that fail silently.
TaskQ refuses boot only on structural stored-config drift, so a whole
family of misconfigurations produces no error at all: an actor with no
stored row never dispatches, a leftover queues row caps an actor
nobody thinks is capped, a stored max_concurrent=0 drains an
actor that looks configured, and a job already pending for an actor
nothing consumes waits forever. Each one's only symptom is work that
does not happen. This is the one command that names them together.
The report leads with the DETECTED storage mode (vanilla, timescale-apache, or timescale-tsl - detected from the server, not read from settings) and that mode's capability consequences in one glance.
Read-only: it issues no writing statement, so it is safe to run against production mid-incident.
Exit code: 0 when the report rendered (every condition reported here
is one a worker keeps running through; a diagnostic that fails the
shell gets wrapped in || true and then ignored). A connection
failure prints doctor failed: <cause> plus an Action line to
stderr and exits 1, migrate up's failure pattern. Gating CI on
drift is taskq actor-config diff, which exits non-zero by design.
Source code in src/taskq/cli.py
1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 | |
health_live ¶
health_ready ¶
health_metrics ¶
ui_serve ¶
ui_serve(
pg_dsn: str | None = typer.Option(
None,
"--pg-dsn",
help="Postgres DSN. Falls back to TASKQ_PG_DSN via dotenvmodel.",
),
schema: str | None = typer.Option(
None,
"--schema",
help="Postgres schema name. Falls back to TASKQ_SCHEMA_NAME via dotenvmodel.",
),
redis_url: str | None = typer.Option(
None,
"--redis-url",
help="Redis URL for real-time mode. Falls back to TASKQ_REDIS_URL via dotenvmodel.",
),
host: str | None = typer.Option(
None,
"--host",
help="Bind address. Falls back to TASKQ_ADMIN_HOST via dotenvmodel.",
),
port: int | None = typer.Option(
None,
"--port",
help="Bind port. Falls back to TASKQ_ADMIN_PORT via dotenvmodel.",
),
run_migrate: bool = typer.Option(
False,
"--migrate",
help="Apply pending migrations before starting. Aborts startup if migrations fail.",
),
pg_credential_provider: str | None = typer.Option(
None,
"--pg-credential-provider",
help=f"Module:attr reference to a PgCredentialProvider (e.g. {_PROVIDER_EXAMPLE}). The admin pool (and --migrate) authenticate through it instead of the DSN's static password. Overrides TASKQ_PG_CREDENTIAL_PROVIDER.",
),
redis_credential_provider: str | None = typer.Option(
None,
"--redis-credential-provider",
help="Module:attr reference to a RedisCredentialProvider for the real-time mode client. Overrides TASKQ_REDIS_CREDENTIAL_PROVIDER.",
),
) -> None
Start the admin UI server on the given host:port.
Source code in src/taskq/cli.py
2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 | |
main ¶
workgroup_start ¶
workgroup_start(
config: Annotated[
Path,
Argument(
help="Path to the workgroup TOML configuration file."
),
],
) -> None
Start a workgroup supervisor that manages multiple worker processes.
The supervisor spawns one taskq worker subprocess per [[workers]]
entry in the config file, monitors their health, restarts them on crash,
and propagates shutdown signals.
Source code in src/taskq/cli.py
workgroup_validate ¶
workgroup_validate(
config: Annotated[
Path,
Argument(
help="Path to the workgroup TOML configuration file."
),
],
) -> None
Validate a workgroup TOML config without starting any workers.
Source code in src/taskq/cli.py
queues_list ¶
List every configured queue row.
Queues absent from this list are not missing -- queues are implicit and
are created by enqueueing onto them. An absent queue runs on the
defaults: strict_fifo ordering (so fairness_key has no effect) and no
concurrency cap.
Source code in src/taskq/cli.py
queues_get ¶
Show one queue's stored configuration.
queues_set_mode ¶
queues_set_mode(
name: Annotated[str, Argument(help="Queue name.")],
mode: Annotated[
str,
Argument(
help=f"Dispatch ordering mode. One of: {join(QUEUE_MODES)}."
),
],
) -> None
Set a queue's dispatch ordering mode, creating the row if needed.
round_robin is what makes fairness_key do anything: on the default
strict_fifo the key is accepted, stored, and ignored. Takes effect on
the next dispatch cycle -- no worker restart.
Source code in src/taskq/cli.py
queues_set_max_concurrent ¶
queues_set_max_concurrent(
name: Annotated[str, Argument(help="Queue name.")],
max_concurrent: Annotated[
int | None,
Option(
--max - concurrent,
min=1,
help="New per-queue leased-slot cap (>= 1; pass --clear for uncapped).",
),
] = None,
clear: Annotated[
bool,
Option(--clear, help="Remove the cap (unlimited)."),
] = False,
) -> None
Set or clear a queue's fleet-wide leased-slot concurrency cap.
Unlike actor-config set --max-concurrent, this is read once at worker
startup, so it needs a worker restart to take effect. There is no 0
state: NULL (via --clear) is uncapped, and an emergency drain to 0
belongs to actor-config set --max-concurrent 0, which is per-actor.
Source code in src/taskq/cli.py
queues_depth ¶
Per-queue depth: pending/scheduled/running/failed counts and the age of the oldest pending job, newest activity first.
Source code in src/taskq/cli.py
insights ¶
insights(
surface: Annotated[
str,
Argument(
help=f"Which insight to render: one of {join(_INSIGHTS_SURFACES)}. Default: all."
),
] = "all",
window: Annotated[
str,
Option(
--window,
help=f"Trailing window for the windowed reads (wait, drain, cron). One of: {join(INSIGHTS_WINDOWS)}. A window older than the archive retention answers over the surviving chunks — the retention floor is the analytics floor.",
),
] = "1h",
actor: Annotated[
str | None,
Option(
--actor,
help="wait only: switch the wait distribution to the per-(actor, queue) grouping and keep this actor's rows. The plain per-queue read folds every actor's attempts together; the actor view separates them.",
),
] = None,
queue: Annotated[
str | None,
Option(
--queue,
help="Keep only this queue's rows (wait, balance, drain). Refused on cron: the fan-out ledger is per schedule and has no queue dimension.",
),
] = None,
) -> None
Render the operational-insights SQL layer as operator tables.
Read-only like taskq doctor: every statement is a SELECT over BOTH
retention tiers (live jobs + jobs_archive), the command issues no
write, and it exits 0 whether or not there is anything to report.
Safe to run against production mid-incident.
The surfaces and their columns:
-
wait— how long terminalised attempts WAITED before their claim, per queue. Columns:count(terminalised attempts in the window),p50/p95/max(the wait distribution),segment. Wait per attempt isstarted_at - scheduled_at(the dispatch claim latency).segment=cleanfor first-delivery attempts (snooze_count = 0andrate_limit_blocked_count = 0) — the subset a queue-latency SLO is written against;segment=deferredfor rows whosescheduled_ata snooze or a rate limit moved forward: RESCHEDULES ARE EXCLUDED from the clean percentiles, and a deferred row's wait measures only the FINAL leg (the deferred time is by construction not in the number).--actorswitches to the per-(actor, queue) grouping and keeps one actor's rows. -
balance— is the fleet imbalanced, per queue. Columns:depth(pending rows due now — the work a worker could claim this instant),live(workers subscribed to the queue and heartbeating in the last 30s),effective_capacity(the routed actors' summedmax_concurrentx live workers: the queue's in-flight ceiling under the current fleet),utilization(depth ÷ effective capacity),oldest_due(age of the oldest due job: a large depth with a tiny age is a burst, a small depth with a large age is a strand),flag. Rows with utilization > 1 are MARKED!! over threshold(more due work than one wave of capacity can absorb);!! no capacitymarks the starvation shape (no live worker or no actor capacity serves the queue). -
drain— seconds-to-drain, per queue: due depth ÷ completions per second over the window. This is a THROUGHPUT extrapolation, not a promise: it assumes the next window looks like the last one, that the queue's workers stay up, and that nothing enqueues behind the current depth. A queue whose window carried no completions has NO estimate: its eta rendersno traffic in window— never 0, which would read as "already drained". Widen--window(up to the archive retention floor) before trusting anything else. -
cron— the per-schedule fan-out ledger. Columns:schedule(the schedule id, shortened),actor,cron,fires(jobs the schedule enqueued in the window),cleared(of those, terminalised),outstanding(the schedule's non-terminal backlog right now, windowless),verdict.!! runaway trendingmarks fires > cleared in BOTH the current and the prior equal window — two consecutive windows of fan-out outrunning clearance is the runaway shape; one window is a burst. Confounds: a DSTallofschedule legitimately doubles a fire in the overlap hour (read the verdict against the schedule's dst_strategy), and a budget-deferred fire enqueues NO row, so a deferral reads as zero fires here — its record is thetaskq.cron.budget_deferralscounter, not this ledger. -
all(the default) — every surface, in the order above.
Source code in src/taskq/cli.py
2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 | |
job_show ¶
job_show(
job_id: Annotated[str, Argument(help="Job id (UUID).")],
show_traceback: Annotated[
bool,
Option(
--traceback,
help="Also print the stored error_traceback (can be large).",
),
] = False,
show_payload: Annotated[
bool,
Option(
--payload,
help="Also print the stored payload jsonb (can be large).",
),
] = False,
) -> None
Show one job's stored row, from jobs or jobs_archive.
Source code in src/taskq/cli.py
job_cancel ¶
job_cancel(
job_id: Annotated[str, Argument(help="Job id (UUID).")],
reason: Annotated[
str | None,
Option(
--reason,
help="Recorded on the job's cancel_request event.",
),
] = None,
) -> None
Cancel one job: pending/scheduled goes straight to terminal
cancelled; running gets a cooperative cancel request the worker's
cancel controller acts on.
Source code in src/taskq/cli.py
job_retry ¶
Re-pend a resting job (every terminal status, including succeeded
and abandoned). The attempt counter is not reset.
Source code in src/taskq/cli.py
job_cancel_where ¶
job_cancel_where(
queue: Annotated[
str | None,
Option(
--queue, help="Cancel only jobs on this queue."
),
] = None,
status: Annotated[
list[str] | None,
Option(
--status,
help="Cancel only jobs in this status. Repeatable. Valid: pending, scheduled, running, succeeded, failed, cancelled, crashed, abandoned.",
),
] = None,
actor: Annotated[
str | None,
Option(
--actor,
help="Cancel only jobs for this actor name.",
),
] = None,
tag: Annotated[
list[str] | None,
Option(
--tag,
help="Cancel only jobs carrying this tag. Repeatable.",
),
] = None,
older_than: Annotated[
str | None,
Option(
--older - than,
help="Only jobs enqueued before now minus this duration (e.g. 45, 30m, 2h, 7d, 2w).",
),
] = None,
reason: Annotated[
str | None,
Option(
--reason,
help="Recorded on every matched job's cancel_request event.",
),
] = None,
dry_run: Annotated[
bool,
Option(
--dry - run,
help="Print the matching count and sample ids; write nothing.",
),
] = False,
) -> None
Bulk cancel every job matching the given filters.
Refuses to run with no filter at all: a naked full-table cancel is the one mistake this command cannot walk back.
Source code in src/taskq/cli.py
job_events ¶
List one job's job_events timeline (kind, occurred_at, detail).