Cleanup#
Erasing recorded entities and their provenance with knit remove, from a single row to a whole lineage.
Remove an entity and its dependents#
Erase a setup, resource, job, run, or command by name, type, or id, cascading down the provenance graph to everything that used it.
APIs: remove:setup, remove:resource, remove:job, remove:run, remove:command, remove:artifact
knit remove erases a recorded entity from the database together with the
rows, provenance edges, and on-disk directories that depend on it — the safe,
provenance-aware alternative to leaving stale setups and runs to accumulate
forever. There is one subcommand per kind (remove setup, remove
resource, remove job, remove run, remove command, remove
artifact), and each takes exactly one selector:
--id <id>— one row by its recorded id (remove run --idalso accepts the id of the app row that run launched);--name <name>— a setup / resource / job instance by its instance name;--type <type>— every setup / resource / job of a type, every run of an app, or every invocation of a command, at once;--group <group>— every job in a submission group (remove jobonly);--path <path>— an artifact by its artifacts-relative path (remove artifactonly).
$ ./exp.sh remove setup --name buildenv
$ ./exp.sh remove job --id "$id"
$ ./exp.sh remove resource --type dataset
By default remove cascades downward: it follows call and produced
edges from the selection, and used_by edges only outward from a provider.
So removing a provider (a setup or a resource) also erases every consumer
that used it — the jobs, their runs, and their artifacts. Removing a
consumer (a job) does the opposite: the setup and resource it used are left
in place, and only the used_by edge into the erased job is dropped.
remove prints an itemized report of exactly what it will erase and asks for
confirmation. Pass --yes to skip the prompt (the report is still printed) so
remove is usable from a script; a non-interactive shell without --yes
declines safely rather than deleting. See Preview a removal with –dry-run to
inspect the blast radius first.
Two guards keep the graph consistent. Selecting a callee whose caller is kept
(an artifact on its own, a run whose job stays) is refused with a hint to remove
the caller instead or pass --from-root (see Erase a whole lineage with
–from-root). And a job that has not finished (submitted / running /
prepared) in the erase set is a hard refusal — stop it first with job
cancel.
Preview a removal with –dry-run#
Print the exact rows, edges, and files a remove would erase without deleting anything or prompting.
APIs: remove:setup, remove:job
A cascade can reach further than expected — removing one provider may erase
every job that used it. --dry-run computes and prints the full erase set, then
exits without prompting and without deleting, so you can inspect the blast
radius before committing:
$ ./exp.sh remove setup --type env --dry-run
The following will be permanently erased:
Data rows (4):
setup env (buildenv) 01a0...
job 01a0... (state: completed)
job crunch 01a0... (body row)
artifact result.txt 01a0...
Provenance edges (6):
fetch:srcpkg 01a0... --used_by--> setup:env 01a0...
...
Directories and artifacts removed (3):
setups/buildenv
jobs/01a0...
artifacts/result.txt
The report has the same three sections remove always prints — the data rows
(setup rows show type (name)), the provenance edges, and the directories and
artifact files — plus a Left on disk section when a file is deliberately
kept (see Keep the files while pruning provenance). The report is exactly what
the confirmation prompt would show; --dry-run just stops before the prompt.
--dry-run changes neither the database nor the filesystem, so it is the way to
check what a --type or --from-root selection actually pulls in before
running it for real.
Erase a whole lineage with –from-root#
Point at any row (or an artifact) and erase the entire call/produced tree it belongs to, both up to the root and back down.
APIs: remove:artifact, remove:job
By default remove only cascades downward and refuses to erase a row whose caller or producer is kept — so naming an artifact on its own fails, because its producing invocation would be left dangling:
$ ./exp.sh remove artifact --path result.txt
[knit:fatal] remove: cannot erase artifact ...; it was produced by
"submit:crunch" ..., which is not being erased. Pass --from-root to erase the
whole lineage.
--from-root widens the erase set to the whole call/produced lineage tree
the selection belongs to. remove first walks up call and produced
edges to the root of the tree, then removes the entire tree downward — so
pointing at any row, or at an artifact, erases everything in its lineage:
$ ./exp.sh remove artifact --path result.txt --from-root
This is the tool for pruning a complete experiment branch: the producing command,
its parent submission, sibling outputs, and every artifact in the tree all go
together. used_by edges are never followed either way, so a setup or
resource that the tree used is still left intact — --from-root widens along
the lineage, not along shared providers. Combine it with --dry-run first to
see the full tree before committing.
Keep the artifacts while pruning provenance#
Erase the database rows and edges of a removal but leave the artifact files on disk, listed under “Left on disk”.
APIs: remove:job, remove:artifact
Sometimes you want a run out of the database but its output files kept —
to archive them by hand, or hand them off before pruning the provenance.
--keep-artifacts does exactly that: it erases the database side as usual
(every row and provenance edge in the set, including each artifact’s
artifacts row and its produced edge) but does not delete the on-disk
artifact entries under artifacts/:
$ ./exp.sh remove job --id "$id" --keep-artifacts --yes
Erased:
...
The following files/directories were NOT removed:
artifacts/result.txt (artifact, --keep-artifacts)
The kept entries are listed in a Left on disk section so the record of what
survived is explicit. Scope is artifacts only: job, setup, and resource
directories are framework-managed containers and are still removed (keeping them
would just orphan them). To keep the whole on-disk tree instead — directories
included — use --keep-files (see the next recipe). Plain (non-artifact)
command outputs are always left on disk and listed there too, with or without
the flag — remove never deletes a file you wrote outside the artifacts/
root.
Because the artifact rows are gone, the kept files are no longer tracked: they
will not be re-checksummed, exported, or found by a provenance walk.
--keep-artifacts is about salvaging bytes while discarding the record, not
about keeping a partial record.
Remove the records only, keep every file#
Erase the database rows and edges of a removal while making no filesystem change at all — every directory, artifact, and plain output stays.
APIs: remove:job, remove:artifact
When you want the provenance out of the database but the whole on-disk tree
left exactly as it is, use --keep-files. It erases the database side as usual
(every row and provenance edge in the set) but makes no filesystem change:
job, setup, and resource directories, artifact entries, and plain outputs all
stay:
$ ./exp.sh remove job --id "$id" --keep-files --yes
Erased:
...
The following files/directories were NOT removed:
jobs/018f9c3a-... (job directory, --keep-files)
artifacts/result.txt (artifact, --keep-files)
Everything that stays is listed under Left on disk, tagged by kind, so the
record of what survived is explicit. This is the superset of
--keep-artifacts: that flag keeps only the artifact entries and still removes
the directories, while --keep-files keeps the directories too. If you pass
both, --keep-files wins.
Because the rows and edges are gone, none of the kept files are tracked any more:
they will not be re-checksummed, exported, or found by a provenance walk.
--keep-files discards the record while leaving the bytes untouched.
Find and erase failed invocations#
Record each command’s exit status in __exit_status__, query which invocations failed, and prune them all at once with remove –failed.
APIs: knit_with_table, knit_no_record_on_failure, remove
Every recorded command also records its exit status. A command that declares
a table (knit_with_table) and is not marked knit_no_record_on_failure
gets a reserved __exit_status__ column, written on every invocation: 0 on
success, the non-zero code on failure. A failed command keeps its row — and any
output or artifact it produced before failing — so a failure deep in a chain
stays queryable instead of vanishing:
# A command that records a row and may fail. Because it declares a table and is
# NOT @no_record_on_failure, knit adds a reserved __exit_status__ column and
# records it on every invocation: 0 on success, the non-zero code on failure. A
# failed row stays in the database (with any output it produced) so you can query
# what failed, and prune it later with `remove --failed`.
@command "boom" "Fail when --code is non-zero, so a failed row is recorded."
@with_table
@with_optional "code:integer" "0" "Exit code to return."
_boom() {
return "$(knit_get_parameter code "$@")"
}
@done
# A command that records a row and may fail. Because it declares a table and is
# NOT @no_record_on_failure, knit adds a reserved __exit_status__ column and
# records it on every invocation: 0 on success, the non-zero code on failure. A
# failed row stays in the database (with any output it produced) so you can query
# what failed, and prune it later with `remove --failed`.
knit_register "boom" _boom "Fail when --code is non-zero, so a failed row is recorded."
knit_with_table
knit_with_optional "code:integer" "0" "Exit code to return."
_boom() {
return "$(knit_get_parameter code "$@")"
}
knit_done
Because the row survives, you can ask the database which invocation failed, and with which parameters — exactly what you need to diagnose it:
$ ./exp.sh query sql --format column --header --exec \
"SELECT id, code, __exit_status__ FROM boom WHERE __exit_status__ <> 0"
__exit_status__ is an ordinary column: it shows up in knit db show,
knit query, and the knit describe schema view like any other. NULL
(an outcome not yet known) and a row back-filled by migration are both “unknown”
and count as neither success nor failure.
To clean up, remove --failed selects every invocation with a non-zero
exit status at once, across every table that carries the column:
$ ./exp.sh remove --failed --dry-run # preview the failed set
$ ./exp.sh remove --failed --yes # erase the failed rows
$ ./exp.sh remove --failed --from-root --yes # erase the jobs that contain them too
--failed uses the same closure and guards as every remove subcommand. On
its own it erases the failed rows, but a failed callee whose caller is kept is
refused — a failed run inside a job that stays, for instance — with a hint to
pass --from-root, which widens to the whole lineage so the enclosing job is
erased too (see Erase a whole lineage with –from-root). There is no per-code
selector: --failed means any non-zero code. To remove by a specific code,
write the SELECT yourself and pass the ids to remove <kind> --id.
Scope it to one kind#
--failed is also a filter on each row-kind subcommand, where it composes
with the selector. On its own it means every failed one of that kind; with a
selector it narrows to the failed rows the selector chose (an AND):
$ ./exp.sh remove command --failed # every failed plain command
$ ./exp.sh remove setup --failed --type buildenv # failed buildenv setups only
$ ./exp.sh remove run --failed --from-root --yes # failed runs and their jobs
So failures are prunable one kind at a time instead of all at once. A failed
setup / resource / command is a row of that kind with a non-zero
status; a failed job or run is one whose body returned non-zero (a job
that tolerated a failed run inside it did itself succeed, so it is not a failed
job — reach the run with remove run --failed). A selector that matches
nothing is still an error, but a selection that simply contains no failures is a
quiet “nothing to erase”. remove artifact has no --failed: an artifact is
produced, not invoked, so it has no exit status.
A command marked knit_no_record_on_failure is the exception: it records no row
on failure, so it has no __exit_status__ column and never appears in the
--failed set.