Lineage
Show the chain of snapshots of a volume, find its tip, and delete a snapshot or a version from the chain.
Last updated
On this page
Overview
The lineage of a volume is the chain of its snapshots. Each snapshot names its parent, the snapshot that it was taken after. The lineage shows the lineage newest first. A snapshot stays in the lineage until it expires or you delete it.
backend-base@3 ← backend-base@2 ← backend-base@1 tip (main) = backend-base@3"before the refactor" "add tests" "first push"Sync points are not part of the lineage. A clone makes one from its source. They are never listed, are not a restore target, and cannot be deleted by hand.
| Part | Description |
|---|---|
id | The name of the snapshot. It starts with the volume id. Restore, clone, and delete take it. |
version | The number of the push under the name of its volume, or null. See Names and versions. |
parent | The snapshot that came before this one. null for the first snapshot. |
parent_version | The version of the parent. Only the versions list shows it. null when the parent has no number or is deleted. |
message | An optional note from the push. |
createdAt | The time the snapshot was taken. |
phase | The progress of the snapshot. See States. |
state | What the source was made of at the time. See Snapshot types. |
saved_by | The pipeline, run, and job that made the version, or null for a push by hand. Only the versions list shows it. |
The lineage lists every snapshot of the volume that you own, in every phase, also error. The versions list shows only snapshots that have a number.
Order and tip
The lineage puts the snapshot with the longest chain of parents first. Snapshots with an equal chain length come newest first. The first row is main, the tip of the lineage. main can be in any phase.
A name reference works on version numbers, not on this order. go-base is the ready version with the highest number. After a branch, main and go-base can name different snapshots.
Deleting a snapshot does not change the parent of the snapshots after it. Their parent still names the deleted id.
Working copies
Add include=working to the history request to see where each working copy stands. The answer is {history, working} instead of the bare array.
Each working entry has kind, name, id, state, base, and head. base is the snapshot the working copy was built from. For a clone, it is the newest push of the source at the time of the clone. head is the latest push of the working copy.
Each history row also has working_copies. It lists the names of the working copies whose head is that row.
Restore and clone
A restore does not change the lineage. It makes a new working copy of the volume at a past snapshot. The first push of that working copy takes the restored snapshot as its parent. The lineage then branches.
backend-base@5 ← backend-base@3 ← backend-base@2 ← backend-base@1 ↑ backend-base@4 ────┘Here the workspace backend pushed versions 1, 2, 3, and then 5. A second workspace was restored at version 1 and pushed version 4. The lineage order is 5, 3, 4, 2, 1. A version number counts pushes of the volume, not depth.
A push takes as its parent the newest ready snapshot that the same workspace or environment pushed. When it pushed none, the parent is the snapshot it started from. These rules follow from the push:
- A workspace made by a restore starts from the snapshot that you named.
- A workspace or an environment made by a clone starts from the sync point that the clone made. The first push names that sync point as
parent. No row of the lineage has thatid. - An environment that you restore in place keeps its own pushes. Its next push chains on its newest
readysnapshot, not on the restored one. - An environment made from a snapshot starts from that snapshot. A clone of the live state of an environment uses the volume of its source. It starts from the sync point of the clone.
- A pipeline job with
saveToadds a version to the volume.saved_bynames the pipeline, the run, and the job.
Restore and clone from a ready snapshot move its expiry to a full default lifetime from now. See Expiry.
Delete from the lineage
You delete one snapshot at a time. A delete is immediate. It is refused with 409 for a sync point and for a snapshot that a running workspace or environment stands on.
A pipeline can read a snapshot. A pipeline reads a version when its latest version pins it, as go-base@2, or names its snapshot id. A pipeline that names go-base with no number reads the last ready snapshot of the volume. A queued or running run reads what it started from. The refusal names the readers.
When you delete the last snapshot of a detached volume, the volume is deleted too.
To do these tasks with an AI agent, see Agentic workflows.
Tasks
List the lineage
Reason for the task
Find the snapshots of a volume in order, with their ids and parents.
Job set-up
- Access: you, or a member of the team that owns the volume.
- Conditions: you know the volume id. Read it from the
volumefield of the workspace or the environment, or see List the volumes. - References:
GET /v1/volumes/{key}/history.
Procedure
Show the snapshots, newest first.
for row in kl.volumes.history(volume="ws-0a1b2c3d4e5f6a7b"): print(row["id"], row["version"], row["parent"], row["createdAt"], row["phase"], row["message"])const rows = (await kl.volumes.history({ volume: "ws-0a1b2c3d4e5f6a7b" })) as Record<string, unknown>[];for (const row of rows) { console.log(row.id, row.version, row.parent, row.createdAt, row.phase, row.message);}rows, err := kl.Volumes.History("ws-0a1b2c3d4e5f6a7b")if err != nil { log.Fatal(err)}for _, row := range rows { fmt.Println(row["id"], row["version"], row["parent"], row["createdAt"], row["phase"], row["message"])}kl.volumes.history(volume: "ws-0a1b2c3d4e5f6a7b").each do |row| puts row.values_at("id", "version", "parent", "createdAt", "phase", "message").join(" ")end// The SDK returns the answer as raw JSON. Read the fields with your own JSON library.System.out.println(kl.volumes().history("ws-0a1b2c3d4e5f6a7b"));kode snapshots list ws-0a1b2c3d4e5f6a7b
Close-out
- Write down the
idof the snapshot for a restore, a clone, or a delete.
List the versions of a volume
Reason for the task
Find the version numbers of a named volume, their parents, and which of them a pipeline saved.
Job set-up
- Access: you, or a member of the team that owns the volume.
- Conditions: the volume has a name. See List the volumes.
- References:
GET /v1/volumes/{key}/{name}/versions.
Procedure
Show the versions, newest first.
for v in kl.volumes.versions(owner="acme", name="go-base"): print(v["version"], v["snapshot"], v["phase"], v["parent_version"], v["expires_at"], v["saved_by"])const versions = (await kl.volumes.versions({ owner: "acme", name: "go-base" })) as Record<string, unknown>[];for (const v of versions) { console.log(v.version, v.snapshot, v.phase, v.parent_version, v.expires_at, v.saved_by);}versions, err := kl.Volumes.Versions("acme", "go-base")if err != nil { log.Fatal(err)}for _, v := range versions { fmt.Println(v["version"], v["snapshot"], v["phase"], v["parent_version"], v["expires_at"], v["saved_by"])}kl.volumes.versions(owner: "acme", name: "go-base").each do |v| puts v.values_at("version", "snapshot", "phase", "parent_version", "expires_at").join(" ")end// The SDK returns the answer as raw JSON. Read the fields with your own JSON library.System.out.println(kl.volumes().versions("acme", "go-base"));kode volumes versions acme/go-base
Close-out
- Make sure that the newest
readyversion has the number that you expect. A gap in the numbers is normal.
Show the tip
Reason for the task
Find the snapshot at the head of the lineage.
Job set-up
- Access: you, or a member of the team that owns the volume.
- Conditions: you know the volume id. See List the lineage.
- References:
GET /v1/volumes/{key}/refs.
Procedure
Show the snapshot in
latest. The value isnullfor a volume with no snapshot.print(kl.volumes.refs(volume="ws-0a1b2c3d4e5f6a7b")["latest"])const refs = (await kl.volumes.refs({ volume: "ws-0a1b2c3d4e5f6a7b" })) as Record<string, unknown>;console.log(refs.latest);refs, err := kl.Volumes.Refs("ws-0a1b2c3d4e5f6a7b")if err != nil { log.Fatal(err)}fmt.Println(refs["latest"])puts kl.volumes.refs(volume: "ws-0a1b2c3d4e5f6a7b")["latest"]// The SDK returns the answer as raw JSON. Read the fields with your own JSON library.System.out.println(kl.volumes().refs("ws-0a1b2c3d4e5f6a7b"));kode snapshots refs ws-0a1b2c3d4e5f6a7b
Close-out
- Make sure that the
idis the first row of the lineage, as in task 001.
Delete a snapshot from the lineage
Reason for the task
Remove a snapshot that you do not need, and release its quota.
Job set-up
- Access: you, or a member of the team that owns the volume.
- Conditions: no running workspace or environment uses the snapshot. No pipeline reads it.
- References:
DELETE /v1/volumes/{key}/snapshots/{snapshot}.
The deletion is immediate and final. If it is the last snapshot of a detached volume, the volume and its data are deleted too.
Procedure
Delete the snapshot. Use the
idfrom the lineage forsnapshot.kl.volumes.delete_snapshot(volume="ws-0a1b2c3d4e5f6a7b", snapshot="ws-0a1b2c3d4e5f6a7b-8a7b6c5d")await kl.volumes.deleteSnapshot({ volume: "ws-0a1b2c3d4e5f6a7b", snapshot: "ws-0a1b2c3d4e5f6a7b-8a7b6c5d" });if err := kl.Volumes.DeleteSnapshot(kloudlite.DeleteSnapshot{Volume: "ws-0a1b2c3d4e5f6a7b", Snapshot: "ws-0a1b2c3d4e5f6a7b-8a7b6c5d"}); err != nil { log.Fatal(err)}kl.volumes.delete_snapshot(volume: "ws-0a1b2c3d4e5f6a7b", snapshot: "ws-0a1b2c3d4e5f6a7b-8a7b6c5d")kl.volumes().deleteSnapshot(new Kloudlite.DeleteSnapshot().volume("ws-0a1b2c3d4e5f6a7b").snapshot("ws-0a1b2c3d4e5f6a7b-8a7b6c5d"));kode snapshots delete ws-0a1b2c3d4e5f6a7b ws-0a1b2c3d4e5f6a7b-8a7b6c5d
Close-out
- List the lineage, as in task 001. Make sure that the
idis gone, or that the call returns404because the volume went with it.
Delete a version from the lineage
Reason for the task
Remove one version of a named volume to free disk, and keep the others.
Job set-up
- Access: you, or a member of the team that owns the volume.
- Conditions: no pipeline pins the version, and no run started from it.
- References:
DELETE /v1/volumes/{key}/{name}/versions/{n}.
The platform deletes the version at once. A job that pins go-base@2 then fails at start.
Procedure
Delete the version.
kl.volumes.delete_version(owner="acme", name="go-base", n=2)await kl.volumes.deleteVersion({ owner: "acme", name: "go-base", n: 2 });if err := kl.Volumes.DeleteVersion("acme", "go-base", 2); err != nil { log.Fatal(err)}kl.volumes.delete_version(owner: "acme", name: "go-base", n: 2)kl.volumes().deleteVersion("acme", "go-base", 2);kode volumes delete-version acme/go-base 2
Close-out
- Show the versions as in task 002. Make sure that the number is gone.
Troubleshooting
| Symptom | Possible cause | Corrective action |
|---|---|---|
404 with not found | The volume or the snapshot does not exist, is not yours, or expired. | List the lineage and use an id from the list. |
404 with volume go-base has no version 9 | The version does not exist or is deleted. | Use a number from the versions. |
| The lineage does not show a snapshot that you expect | The snapshot is a sync point, or another owner pushed it. | Use an id of a snapshot that you own. |
main is not the snapshot with the highest version | The lineage branched after a restore. | Use the version number to select a version. See Order and tip. |
409 with a sync point cannot be deleted by hand | The id is a sync point, not a snapshot. | Use an id from the lineage. |
409 with version 2 of go-base is used by pipeline build | A pipeline or a run reads the version. | Change the pipeline, or wait for the run. See Readers. |
409 with this snapshot is the base of {kind} {display} | A workspace or environment was built from this snapshot. | Delete that workspace or environment, or delete a different snapshot. |
409 with this snapshot is where {kind} {display} stands now (its latest push) | The snapshot is the latest push of a workspace or environment. | Push again, stop it, or delete a different snapshot. |
503 with the cluster could not be reached | The platform cannot check what uses the snapshot. Nothing is deleted. | Wait, then delete again. |