Show human-readable session prune sizes
- commit
- 2470fdad980c2aa3e363b42cb5a3bf709c2de1aa
- author
- patriceckhart · · 2026-08-24 05:16 UTC
- parent
- 9b7bb6a
- signature
- unsigned
- refs
- tag: v0.3.49
3 files · +27 -3
M README.md +1 -1
| @@ -430,7 +430,7 @@ This is a guardrail against accidents, not a hard security boundary. If you need | |||
| 430 | 430 | ||
| 431 | 431 | Every interactive or print/json run (unless `--no-session`) writes a JSONL transcript under `$ZOT_HOME/sessions/<cwd-hash>/`. Resume any of them with `--continue`, `--resume`, `--session <path>`, or interactively via `/sessions` inside the TUI. Empty sessions (the user exited without prompting) are deleted on close so the list stays tidy. | |
| 432 | 432 | ||
| 433 | - | Use `zot sessions prune` outside the TUI to find sessions whose recorded working directories no longer exist. The command groups sessions by directory, shows the number of stored sessions, lets you select groups, and requires confirmation before permanently deleting files. It preserves sessions when a directory check fails with an error other than "not found" and rechecks each selected directory immediately before deletion. A deleted directory and a path hidden by some unmounted filesystems are indistinguishable, so review the selection and ensure remote filesystems are mounted before deleting missing-directory groups. | |
| 433 | + | Use `zot sessions prune` outside the TUI to find sessions whose recorded working directories no longer exist. The command groups sessions by directory, shows the number and human-readable total size of stored sessions, lets you select groups, and requires confirmation before permanently deleting files. It preserves sessions when a directory check fails with an error other than "not found" and rechecks each selected directory immediately before deletion. A deleted directory and a path hidden by some unmounted filesystems are indistinguishable, so review the selection and ensure remote filesystems are mounted before deleting missing-directory groups. | |
| 434 | 434 | ||
| 435 | 435 | Use `--older-than` to prune sessions by last activity instead, including sessions for directories that still exist. Supported age units are `m` (minutes), `h` (hours), `d` (24-hour days), `w` (7-day weeks), `mo` (calendar months), and `y` (calendar years). Add `--cwd PATH` to limit age pruning to one working directory. The session file is checked again immediately before deletion and preserved if it has become active recently. | |
| 436 | 436 | ||
| @@ -430,7 +430,7 @@ This is a guardrail against accidents, not a hard security boundary. If you need | |||||
| 430 | 430 | ||||
| 431 | Every interactive or print/json run (unless `--no-session`) writes a JSONL transcript under `$ZOT_HOME/sessions/<cwd-hash>/`. Resume any of them with `--continue`, `--resume`, `--session <path>`, or interactively via `/sessions` inside the TUI. Empty sessions (the user exited without prompting) are deleted on close so the list stays tidy. | 431 | Every interactive or print/json run (unless `--no-session`) writes a JSONL transcript under `$ZOT_HOME/sessions/<cwd-hash>/`. Resume any of them with `--continue`, `--resume`, `--session <path>`, or interactively via `/sessions` inside the TUI. Empty sessions (the user exited without prompting) are deleted on close so the list stays tidy. | ||
| 432 | 432 | ||||
| 433 | - | Use `zot sessions prune` outside the TUI to find sessions whose recorded working directories no longer exist. The command groups sessions by directory, shows the number of stored sessions, lets you select groups, and requires confirmation before permanently deleting files. It preserves sessions when a directory check fails with an error other than "not found" and rechecks each selected directory immediately before deletion. A deleted directory and a path hidden by some unmounted filesystems are indistinguishable, so review the selection and ensure remote filesystems are mounted before deleting missing-directory groups. | 433 | + | Use `zot sessions prune` outside the TUI to find sessions whose recorded working directories no longer exist. The command groups sessions by directory, shows the number and human-readable total size of stored sessions, lets you select groups, and requires confirmation before permanently deleting files. It preserves sessions when a directory check fails with an error other than "not found" and rechecks each selected directory immediately before deletion. A deleted directory and a path hidden by some unmounted filesystems are indistinguishable, so review the selection and ensure remote filesystems are mounted before deleting missing-directory groups. |
| 434 | 434 | ||||
| 435 | Use `--older-than` to prune sessions by last activity instead, including sessions for directories that still exist. Supported age units are `m` (minutes), `h` (hours), `d` (24-hour days), `w` (7-day weeks), `mo` (calendar months), and `y` (calendar years). Add `--cwd PATH` to limit age pruning to one working directory. The session file is checked again immediately before deletion and preserved if it has become active recently. | 435 | Use `--older-than` to prune sessions by last activity instead, including sessions for directories that still exist. Supported age units are `m` (minutes), `h` (hours), `d` (24-hour days), `w` (7-day weeks), `mo` (calendar months), and `y` (calendar years). Add `--cwd PATH` to limit age pruning to one working directory. The session file is checked again immediately before deletion and preserved if it has become active recently. | ||
| 436 | 436 | ||||
M packages/agent/sessionscmd.go +2 -1
| @@ -14,6 +14,7 @@ import ( | |||
| 14 | 14 | "time" | |
| 15 | 15 | ||
| 16 | 16 | "github.com/patriceckhart/zot/packages/core" | |
| 17 | + | "github.com/patriceckhart/zot/packages/provider" | |
| 17 | 18 | ) | |
| 18 | 19 | ||
| 19 | 20 | type sessionsPruneOptions struct { | |
| @@ -266,7 +267,7 @@ func runSessionsPrune(opts sessionsPruneOptions, in io.Reader, out, errOut io.Wr | |||
| 266 | 267 | fmt.Fprintln(out, "missing session directories:") | |
| 267 | 268 | } | |
| 268 | 269 | for idx, group := range candidates { | |
| 269 | - | fmt.Fprintf(out, " %d. %s (%s, %d bytes)\n", idx+1, group.CWD, sessionCount(len(group.Paths)), group.SizeBytes) | |
| 270 | + | fmt.Fprintf(out, " %d. %s (%s, %s)\n", idx+1, group.CWD, sessionCount(len(group.Paths)), provider.FormatBytes(group.SizeBytes)) | |
| 270 | 271 | } | |
| 271 | 272 | if opts.dryRun { | |
| 272 | 273 | fmt.Fprintf(out, "dry run: %s in %s would be deleted\n", sessionCount(totalSessions(candidates)), directoryCount(len(candidates))) | |
| @@ -14,6 +14,7 @@ import ( | |||||
| 14 | "time" | 14 | "time" | ||
| 15 | 15 | ||||
| 16 | "github.com/patriceckhart/zot/packages/core" | 16 | "github.com/patriceckhart/zot/packages/core" | ||
| 17 | + | "github.com/patriceckhart/zot/packages/provider" | |||
| 17 | ) | 18 | ) | ||
| 18 | 19 | ||||
| 19 | type sessionsPruneOptions struct { | 20 | type sessionsPruneOptions struct { | ||
| @@ -266,7 +267,7 @@ func runSessionsPrune(opts sessionsPruneOptions, in io.Reader, out, errOut io.Wr | |||||
| 266 | fmt.Fprintln(out, "missing session directories:") | 267 | fmt.Fprintln(out, "missing session directories:") | ||
| 267 | } | 268 | } | ||
| 268 | for idx, group := range candidates { | 269 | for idx, group := range candidates { | ||
| 269 | - | fmt.Fprintf(out, " %d. %s (%s, %d bytes)\n", idx+1, group.CWD, sessionCount(len(group.Paths)), group.SizeBytes) | 270 | + | fmt.Fprintf(out, " %d. %s (%s, %s)\n", idx+1, group.CWD, sessionCount(len(group.Paths)), provider.FormatBytes(group.SizeBytes)) |
| 270 | } | 271 | } | ||
| 271 | if opts.dryRun { | 272 | if opts.dryRun { | ||
| 272 | fmt.Fprintf(out, "dry run: %s in %s would be deleted\n", sessionCount(totalSessions(candidates)), directoryCount(len(candidates))) | 273 | fmt.Fprintf(out, "dry run: %s in %s would be deleted\n", sessionCount(totalSessions(candidates)), directoryCount(len(candidates))) | ||
M packages/agent/sessionscmd_test.go +24 -1
| @@ -107,6 +107,24 @@ func TestSessionsPruneDryRunPreservesSessions(t *testing.T) { | |||
| 107 | 107 | } | |
| 108 | 108 | } | |
| 109 | 109 | ||
| 110 | + | func TestSessionsPruneDisplaysHumanReadableGroupSizes(t *testing.T) { | |
| 111 | + | home := t.TempDir() | |
| 112 | + | t.Setenv("ZOT_HOME", home) | |
| 113 | + | missing := filepath.Join(home, "missing-project") | |
| 114 | + | createPruneTestSessionWithText(t, home, missing, strings.Repeat("a", 2048)) | |
| 115 | + | ||
| 116 | + | var out, errOut bytes.Buffer | |
| 117 | + | if err := runSessionsPrune(sessionsPruneOptions{dryRun: true}, strings.NewReader(""), &out, &errOut, os.Stat); err != nil { | |
| 118 | + | t.Fatal(err) | |
| 119 | + | } | |
| 120 | + | if !strings.Contains(out.String(), "KiB") { | |
| 121 | + | t.Fatalf("output = %q, want binary size unit", out.String()) | |
| 122 | + | } | |
| 123 | + | if strings.Contains(out.String(), " bytes)") { | |
| 124 | + | t.Fatalf("output = %q, want human-readable size instead of raw bytes", out.String()) | |
| 125 | + | } | |
| 126 | + | } | |
| 127 | + | ||
| 110 | 128 | func TestSessionsPruneInteractiveDeletesSelectedGroup(t *testing.T) { | |
| 111 | 129 | home := t.TempDir() | |
| 112 | 130 | t.Setenv("ZOT_HOME", home) | |
| @@ -285,6 +303,11 @@ func TestParseSessionsPruneOptions(t *testing.T) { | |||
| 285 | 303 | } | |
| 286 | 304 | ||
| 287 | 305 | func createPruneTestSession(t *testing.T, root, cwd string) string { | |
| 306 | + | t.Helper() | |
| 307 | + | return createPruneTestSessionWithText(t, root, cwd, "hello") | |
| 308 | + | } | |
| 309 | + | ||
| 310 | + | func createPruneTestSessionWithText(t *testing.T, root, cwd, text string) string { | |
| 288 | 311 | t.Helper() | |
| 289 | 312 | session, err := core.NewSession(root, cwd, "test", "test-model", "test-version") | |
| 290 | 313 | if err != nil { | |
| @@ -292,7 +315,7 @@ func createPruneTestSession(t *testing.T, root, cwd string) string { | |||
| 292 | 315 | } | |
| 293 | 316 | if err := session.AppendMessage(provider.Message{ | |
| 294 | 317 | Role: provider.RoleUser, | |
| 295 | - | Content: []provider.Content{provider.TextBlock{Text: "hello"}}, | |
| 318 | + | Content: []provider.Content{provider.TextBlock{Text: text}}, | |
| 296 | 319 | }); err != nil { | |
| 297 | 320 | t.Fatal(err) | |
| 298 | 321 | } | |
| @@ -107,6 +107,24 @@ func TestSessionsPruneDryRunPreservesSessions(t *testing.T) { | |||||
| 107 | } | 107 | } | ||
| 108 | } | 108 | } | ||
| 109 | 109 | ||||
| 110 | + | func TestSessionsPruneDisplaysHumanReadableGroupSizes(t *testing.T) { | |||
| 111 | + | home := t.TempDir() | |||
| 112 | + | t.Setenv("ZOT_HOME", home) | |||
| 113 | + | missing := filepath.Join(home, "missing-project") | |||
| 114 | + | createPruneTestSessionWithText(t, home, missing, strings.Repeat("a", 2048)) | |||
| 115 | + | ||||
| 116 | + | var out, errOut bytes.Buffer | |||
| 117 | + | if err := runSessionsPrune(sessionsPruneOptions{dryRun: true}, strings.NewReader(""), &out, &errOut, os.Stat); err != nil { | |||
| 118 | + | t.Fatal(err) | |||
| 119 | + | } | |||
| 120 | + | if !strings.Contains(out.String(), "KiB") { | |||
| 121 | + | t.Fatalf("output = %q, want binary size unit", out.String()) | |||
| 122 | + | } | |||
| 123 | + | if strings.Contains(out.String(), " bytes)") { | |||
| 124 | + | t.Fatalf("output = %q, want human-readable size instead of raw bytes", out.String()) | |||
| 125 | + | } | |||
| 126 | + | } | |||
| 127 | + | ||||
| 110 | func TestSessionsPruneInteractiveDeletesSelectedGroup(t *testing.T) { | 128 | func TestSessionsPruneInteractiveDeletesSelectedGroup(t *testing.T) { | ||
| 111 | home := t.TempDir() | 129 | home := t.TempDir() | ||
| 112 | t.Setenv("ZOT_HOME", home) | 130 | t.Setenv("ZOT_HOME", home) | ||
| @@ -285,6 +303,11 @@ func TestParseSessionsPruneOptions(t *testing.T) { | |||||
| 285 | } | 303 | } | ||
| 286 | 304 | ||||
| 287 | func createPruneTestSession(t *testing.T, root, cwd string) string { | 305 | func createPruneTestSession(t *testing.T, root, cwd string) string { | ||
| 306 | + | t.Helper() | |||
| 307 | + | return createPruneTestSessionWithText(t, root, cwd, "hello") | |||
| 308 | + | } | |||
| 309 | + | ||||
| 310 | + | func createPruneTestSessionWithText(t *testing.T, root, cwd, text string) string { | |||
| 288 | t.Helper() | 311 | t.Helper() | ||
| 289 | session, err := core.NewSession(root, cwd, "test", "test-model", "test-version") | 312 | session, err := core.NewSession(root, cwd, "test", "test-model", "test-version") | ||
| 290 | if err != nil { | 313 | if err != nil { | ||
| @@ -292,7 +315,7 @@ func createPruneTestSession(t *testing.T, root, cwd string) string { | |||||
| 292 | } | 315 | } | ||
| 293 | if err := session.AppendMessage(provider.Message{ | 316 | if err := session.AppendMessage(provider.Message{ | ||
| 294 | Role: provider.RoleUser, | 317 | Role: provider.RoleUser, | ||
| 295 | - | Content: []provider.Content{provider.TextBlock{Text: "hello"}}, | 318 | + | Content: []provider.Content{provider.TextBlock{Text: text}}, |
| 296 | }); err != nil { | 319 | }); err != nil { | ||
| 297 | t.Fatal(err) | 320 | t.Fatal(err) | ||
| 298 | } | 321 | } | ||