Task Pruning
Pruning permanently deletes old tasks that have completed. It reclaims storage space while maintaining referential integrity.
Philosophy
In an event-sourced system, completed work still consumes space. Pruning selectively removes terminal tasks (done/archived) and their events, balancing storage efficiency with audit trails. The system ensures:
- Family atomicity: Parent and child tasks are pruned together or not at all
- Dependency safety: Tasks that are prerequisites for non-terminal work cannot be pruned
- Projection cleanup: Projections are deleted before events to enable recovery if needed
When to Prune
Prune tasks when:
- Completed work is older than your retention policy (e.g., 30+ days old)
- Storage concerns require reclamation
- Archival needs are met (exported to external systems)
Do not prune:
- Tasks with active dependencies (non-terminal tasks depend on them)
- Tasks in subtask hierarchies where only some children are terminal
- Tasks before archival/export if you need the audit trail
Usage
Basic Pruning
Prune tasks older than 30 days in a specific project:
hzl task prune --project my-project --older-than 30d --yes
Prune across all projects:
hzl task prune --all --older-than 30d --yes
Preview Before Deleting
See what would be deleted without removing anything:
hzl task prune --project my-project --older-than 30d --dry-run
JSON Output
For scripting:
hzl task prune --project my-project --older-than 30d --yes --json
Returns:
{
"pruned": [
{
"task_id": "abc123",
"title": "Completed task",
"project": "my-project",
"status": "done"
}
],
"count": 1,
"eventsDeleted": 5
}
Custom Date Threshold
Evaluate age as of a fixed date (ISO 8601):
hzl task prune --project my-project --older-than 30d --as-of 2026-01-15T00:00:00Z --yes
Tasks older than 30 days from January 15 would be pruned.
Non-Interactive Scripting
In non-TTY environments (CI/cron), always use --yes:
hzl task prune --all --older-than 7d --yes
Without --yes, the command will fail in non-interactive mode.
Safety Features
Family Atomicity
If a parent task has children:
- Both parent and children must be terminal (done/archived) to prune
- Pruning a parent also prunes all children (automatically)
- Cannot prune a child without pruning siblings (enforced by SQL query)
Example: If a parent is done but a child is in-progress, neither can be pruned.
Dependency Safety
Tasks that are prerequisites cannot be pruned if any dependent task is non-terminal.
Example:
- Task A (done) is a dependency of Task B (in-progress)
- Task A cannot be pruned until Task B completes or dependency is removed
Confirmation Prompt
By default, prune prompts for confirmation and previews tasks:
Ready to permanently delete 5 task(s):
Project 'inbox': 5 task(s)
[abc123d4] Old completed task
[def456e7] Another done task
...
WARNING: This action cannot be undone. Events will be permanently deleted.
Type 'yes' to confirm:
Skip the prompt with --yes (for scripting).
Transaction Safety
Pruning operates in a single transaction:
- Projections are deleted first (for recovery safety)
- Events are deleted second
- On failure, the entire operation rolls back
- Never leaves partial deletions
Best Practices
1. Start Conservative
Begin with a long retention period, then shorten:
# First time: 90-day threshold
hzl task prune --project my-project --older-than 90d --dry-run
# After reviewing: reduce to 30 days
hzl task prune --project my-project --older-than 30d --dry-run
2. Preview First
Always use --dry-run to see what will be deleted:
hzl task prune --project my-project --older-than 30d --dry-run
3. Export Before Pruning
If you need an audit trail, export tasks to NDJSON first:
hzl task prune --project my-project --older-than 30d --export backup.ndjson --yes
(Note: --export is reserved for future use; currently use external tooling for backup)
4. Schedule Regularly
Use cron for periodic cleanup:
# Every Sunday, prune tasks older than 30 days
0 0 * * 0 hzl task prune --all --older-than 30d --yes
5. Monitor Storage
Track before/after storage usage:
# Before pruning
du -sh .local/hzl/
# Prune
hzl task prune --all --older-than 30d --yes
# After pruning (consider running VACUUM to reclaim space)
du -sh .local/hzl/
Disk space is not immediately reclaimed; SQLite marks it as reusable. To actually reclaim:
hzl task prune --all --older-than 30d --yes --vacuum
(Note: --vacuum is reserved for future use)
Terminology
- Terminal state: Task is either
doneorarchived - Eligible: Task meets all criteria for pruning (terminal, age threshold, no active dependents, not parent of non-terminal children)
- Family: Parent task and all its children (one level)
- Prunability: Whether a task can be safely deleted
Limitations
- Pruning cannot be undone without a database backup
- Deletes events permanently—no recovery from events database after pruning
- Cannot selectively prune tasks mid-hierarchy (all or nothing per family)
- Age threshold is evaluated from
terminal_attimestamp (when task became terminal)
Troubleshooting
“Cannot prune task X—has active dependents”
Task X is a prerequisite for a non-terminal task. Either:
- Complete the dependent task first
- Remove the dependency relationship
- Wait for the dependent to reach a terminal state
“Cannot prompt for confirmation in non-interactive mode”
Running in a non-TTY environment (CI, cron, background job) without --yes flag. Add --yes to auto-confirm:
hzl task prune --project my-project --older-than 30d --yes
No tasks eligible for pruning
Possible reasons:
- All tasks are younger than the threshold
- Tasks are not in terminal states (still in-progress, ready, blocked)
- Tasks have active dependents or non-terminal children
Use --dry-run and check output.