Stepping in: mark, cancel, pause, backfill, rerun and bypasses¶
In local mode etl-craft is the orchestrator, so it is where you step in when a run needs a hand:
a failure you have fixed by other means, an upstream that cannot run where you are, a task to run
again, a run that must stop now. Every change needs a reason, is recorded in AUD_RUN_INTERVENTIONS with who made
it and when, and erases nothing: each attempt keeps its row, its log file and, in the
intervention, the message it had before.
In remote mode the orchestrator is the only source of truth for runs, so all of these are refused
(exit status 10): mark, clear or stop the task in the orchestrator instead. See
Running under an orchestrator.
Choose the run¶
run, mark, cancel, history and steps accept --run-id 42 or
--run-key 'manual:...'. The identity must belong to --pipeline_code. Without a selector,
operations use the pipeline's single non-terminal run. When none or several exist, the command
refuses and lists candidates with their id, key, trigger kind, run date and status. It never
selects a completed run by recency.
etl-craft history --pipeline_code SALES_DAILY --all
etl-craft history --pipeline_code SALES_DAILY --run-id 42
etl-craft steps --pipeline_code SALES_DAILY --run-id 42
history --all --limit 20 lists runs for finding an identity. steps shows each task's status
under the selected run alongside its current metadata parameters. Creating a run with a whole
run or --init-only needs no existing identity; a missing --run-key creates that key.
--backfill, --skip and mark --new-run create their own new identities and refuse selectors.
Mark a task¶
etl-craft mark --pipeline_code SALES_DAILY --run-id 42 --task_code load_orders --status SUCCESS \
--reason "loaded by hand from the 06:00 file"
The task is set to SUCCESS, FAILED or SKIPPED under the selected run, and its
ERROR_MESSAGE says who marked it and why. A task that is running is refused: cancel the run
first, or wait.
A task left IN-PROGRESS after its supervisor dies is recovered when its lease expires.
Whole-pipeline supervisors and task supervisors hold 60-second leases, renewed every 15 seconds.
A second whole-pipeline command refuses while the run remains owned. Every run reconciles
expired leases before admission, or you can request reconciliation directly:
etl-craft reconcile --pipeline_code SALES_DAILY
etl-craft reconcile --pipeline_code SALES_DAILY --task_code load_orders
Without filters, reconcile checks every pipeline. An expired local attempt becomes LOST and
its task summary becomes FAILED. A verified local child and its observed descendants are
stopped; host, PID and process birth identity protect against signalling a reused PID. An
attempt on another host receives another 120 seconds of grace after expiry. An expired run
with no active attempts becomes unowned and can resume under the same run identity.
LOST means the outcome is uncertain: a warehouse commit or external script side effect may
already have happened. Check the target before retrying operations that cannot safely repeat.
Old attempt owners cannot write outcomes after expiry or after a newer attempt starts.
mark --stale remains a compatibility option: it reconciles this task's expired attempts before
marking. It refuses while the task remains live; it does not override a valid lease.
etl-craft mark --pipeline_code SALES_DAILY --task_code load_orders --status FAILED --stale \
--reason "the host restarted at 03:10"
If an explicitly selected run has ended, it is reopened (IN-PROGRESS again), and the tasks the engine skipped
without running are reset, so running the pipeline again resumes it from there:
Mark a failed task SUCCESS and the tasks waiting on it run; the rest of the run is not repeated.
Mark a task FAILED and it gets another attempt.
A marked SUCCESS satisfies a HAS_DATA dependency only when you state the row count:
etl-craft mark --pipeline_code SALES_DAILY --run-id 42 --task_code load_orders --status SUCCESS \
--rows 1200 --reason "1200 rows loaded by hand"
Mark a run¶
Without --task_code, mark sets the run itself, ending it now if it had not ended:
etl-craft mark --pipeline_code SALES_DAILY --status SUCCESS --reason "load_orders is not needed today"
The pipelines that depend on this one judge the marked status. Its tasks keep theirs, and no upstream run is consumed. A run with a task still running is refused.
Record a stand-in run¶
When a pipeline cannot run where you are (an upstream that only runs in production, say), its
dependents' gates would never pass. mark --new-run records a finished run of it that did not
really run, and the gates judge it like any other:
etl-craft mark --pipeline_code CRM_EXPORT --new-run --status SUCCESS \
--reason "CRM_EXPORT only runs in production"
etl-craft mark --pipeline_code CRM_EXPORT --new-run --task_code publish --status SUCCESS \
--rows 50 --reason "stand-in for a HAS_DATA dependency on CRM_EXPORT.publish"
With --task_code, the task gets a row in the stand-in run too, for dependencies on that task.
A pipeline with a run in progress is refused: mark that run, or cancel it.
Cancel a run¶
The pipeline's run in progress ends CANCELLED, and so does each of its running tasks. The
process running each task looks every two seconds, stops the task's process when it sees the
cancel, and the process running the pipeline starts nothing more; run then exits 1. The next
run --pipeline_code starts a new run.
Pause and resume a pipeline¶
etl-craft pause --pipeline_code SALES_DAILY --reason "the CRM is being migrated this week"
etl-craft resume --pipeline_code SALES_DAILY --reason "the migration is done"
While a pipeline is paused, etl-craft run starts nothing of it (whole runs, single tasks and
--init-only alike): it says the pipeline is paused, by whom and why, and exits 0, so a
scheduler that keeps calling it raises no alarm. A run in progress when the pipeline is paused
lets its running tasks finish, starts no more, and stays IN-PROGRESS; the interrupted run
command exits 22 (INCOMPLETE). After resume, the next
run --pipeline_code goes on with it. etl-craft list and the catalog show every paused
pipeline, and AUD_PIPELINE_PAUSES keeps every pause with who paused and resumed it, when and
why.
Skip a run on purpose¶
Records a run of the pipeline SKIPPED, running nothing, as a stand-in run does (see
Record a stand-in run). The pipelines that depend on it see a run that
did nothing: a SUCCESS dependency on it is not satisfied, an ALWAYS one is.
Backfill over dates¶
etl-craft run --pipeline_code SALES_DAILY --backfill 2026-09-01:2026-09-07 \
--reason "the source resent the first week of September"
Runs the pipeline once for each date, oldest first, each a run of its own as of its date: SQL
tasks read it as $$run_date, scripts as
task.run_date. Backfill runs leave the scheduled runs as they were: they check no dependency on
other pipelines and consume no upstream run (each run records that as a GATE_BYPASS with the
reason), and scripts get no offset and store none. The backfill stops at the first run that does
not end SUCCESS or SKIPPED, and says how to take up from that date once it is fixed. It runs
at most 366 dates, and not while the pipeline has a run in progress or is paused.
A single run can be given its date too: etl-craft run --pipeline_code SALES_DAILY --run-date
2026-09-01. etl-craft history --all shows each run's date, and which were backfill runs.
Run a task without its dependencies¶
etl-craft run --pipeline_code SALES_DAILY --task_code load_orders --ignore-dependencies \
--reason "extract_orders is late; the file is already in place"
The task runs under the pipeline's run in progress without its dependencies being checked, in
its pipeline or elsewhere, and consumes no upstream run. A task that already ended SUCCESS or
SKIPPED is not run again: that is --rerun.
Run a task again¶
etl-craft run --pipeline_code SALES_DAILY --run-id 42 --task_code load_orders --rerun \
--reason "the source resent the file"
etl-craft run --pipeline_code SALES_DAILY --run-id 42 --task_code load_orders --rerun --with-downstream \
--reason "the source resent the file"
The task runs again under the selected run, although it already ended, as a new attempt
on its row that skips nothing (a business-rules task checks every rule again), without its
dependencies being checked. With --with-downstream, every task after it runs again too, in
dependency order, each once its own dependencies are satisfied; those whose dependencies are not
(an alert that waits for a failure, say) keep their rows. A run that had ended is reopened for
this and ended again from its tasks' statuses; a run still in progress is left for
run --pipeline_code to finish.
Relax dependency gates¶
A local run checks its dependencies on other pipelines before it starts, and a task its
dependencies on other pipelines' tasks (see
Dependencies on other pipelines). Where an
upstream cannot run at all, in a development environment, say, Orchestration.Dependency_gates
relaxes that per profile:
enforcechecks every dependency and skips the run or the task when one is not satisfied.warnchecks them all and goes ahead anyway, with a warning.offchecks none.
Each dependency let through is recorded against the run, or the task, as a GATE_BYPASS, and the
run's summary says so. Only upstream runs that satisfied their dependency are consumed. doctor
warns whenever gates are not enforced, and remote mode refuses anything but enforce: there the
orchestrator's sensors are the gates.
What was changed¶
etl-craft history --pipeline_code SALES_DAILY --all lists the interventions on the runs it shows,
under the runs, and the catalog lists those on each pipeline's last run.
AUD_RUN_INTERVENTIONS holds every one:
| Column | Holds |
|---|---|
PIPELINE_RUN_ID, TASK_ID |
the run, and the task (NULL for a change to the run itself) |
ACTION |
MARK, NEW_RUN, CANCEL, REOPEN (a run a mark or a rerun reopened), RESET (a skipped task reset to run again), RERUN, IGNORE_DEPENDENCIES, or GATE_BYPASS |
FROM_STATUS, TO_STATUS |
the status before and after; TO_STATUS is NULL for a reset |
TARGET_COUNT |
the row count stated with --rows |
PREVIOUS_MESSAGE |
the row's ERROR_MESSAGE before the change |
REASON, REQUESTED_BY, REQUESTED_AT |
why (for a bypass, the dependencies let through), who (user@host) and when |