Files
beads/docs/DELETIONS.md
Steve Yegge d80d82d693 docs: update deletion docs for tombstones, add changelog entries
- Rewrote DELETIONS.md to document inline tombstones (replacing legacy
  deletions.jsonl approach)
- Added tombstone feature entries to CHANGELOG.md under Unreleased
- Fixed duplicate 0.29.0 header in CHANGELOG.md
- Ran bd migrate-tombstones on beads repo (dogfooding)
- Closed bd-vw8 epic (all 12 dependencies complete)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-13 07:28:38 -08:00

5.7 KiB

Deletion Tracking

This document describes how bd tracks and propagates deletions across repository clones.

Overview

When issues are deleted in one clone, those deletions need to propagate to other clones. Without this mechanism, deleted issues would "resurrect" when another clone's database is imported.

Beads uses inline tombstones - deleted issues are converted to a special tombstone status and remain in issues.jsonl. This provides:

  • Full audit trail (who, when, why)
  • Atomic sync with issue data (no separate manifest to merge)
  • TTL-based expiration (default 30 days)
  • Proper 3-way merge conflict resolution

How Tombstones Work

When you delete an issue:

  1. The issue's status changes to tombstone
  2. Deletion metadata is recorded (deleted_at, deleted_by, delete_reason)
  3. The original issue type is preserved in original_type
  4. All dependencies are removed (tombstones don't block anything)
  5. The tombstone syncs via git like any other issue

Tombstone Fields

Field Type Description
status string Always "tombstone"
deleted_at ISO 8601 When the issue was deleted
deleted_by string Actor who performed the deletion
delete_reason string Optional context (e.g., "duplicate", "cleanup")
original_type string Issue type before deletion (task, bug, etc.)

Example Tombstone in JSONL

{"id":"bd-42","status":"tombstone","title":"Original title","deleted_at":"2025-01-15T10:00:00Z","deleted_by":"stevey","delete_reason":"duplicate of bd-xyz","original_type":"task"}

Commands

Deleting Issues

bd delete bd-42                    # Delete single issue (preview mode)
bd delete bd-42 --force            # Actually delete
bd delete bd-42 bd-43 bd-44 -f     # Delete multiple issues
bd delete bd-42 --cascade -f       # Delete with all dependents
bd delete --from-file ids.txt -f   # Delete from file (one ID per line)
bd delete bd-42 --dry-run          # Preview what would be deleted

Viewing Deleted Issues

bd list --status=tombstone         # List all tombstones
bd show bd-42                      # View tombstone details (if you know the ID)

TTL and Expiration

Tombstones expire after a configurable TTL (default: 30 days). This prevents unbounded growth while ensuring deletions propagate to all clones.

How Expiration Works

  1. Tombstones older than TTL + 1 hour grace period are eligible for pruning
  2. bd compact removes expired tombstones from issues.jsonl
  3. Git history fallback handles edge cases where pruned tombstones are needed

Configuration

# .beads/config.yaml
tombstone:
  ttl_days: 30        # Default: 30 days

Or via CLI:

bd config set tombstone.ttl_days 60

Manual Pruning

bd compact                         # Prune expired tombstones (and other compaction)

Conflict Resolution

When the same issue is modified in one clone and deleted in another:

  1. Both changes sync via git
  2. 3-way merge detects the conflict
  3. Resolution rules:
    • If tombstone is expired → live issue wins (resurrection)
    • If tombstone is fresh → tombstone wins (deletion propagates)
    • updated_at timestamps break ties

This ensures deletions propagate reliably while handling clock skew and delayed syncs.

Migration from Legacy Format

Prior to v0.30, beads used a separate deletions.jsonl manifest. To migrate:

bd migrate-tombstones              # Convert deletions.jsonl to inline tombstones
bd migrate-tombstones --dry-run    # Preview changes first

The migration:

  1. Reads existing deletions from deletions.jsonl
  2. Creates tombstone entries in issues.jsonl
  3. Archives the old file as deletions.jsonl.migrated

After migration, run bd sync to propagate tombstones to other clones.

Troubleshooting

Deleted Issue Reappearing

If a deleted issue reappears after sync:

# Check if it's a tombstone
bd list --status=tombstone | grep bd-xxx

# Check tombstone details
bd show bd-xxx

# Force re-import from JSONL
bd import --force

If the issue keeps reappearing, the tombstone may have expired. Re-delete it:

bd delete bd-xxx --force
bd sync

Tombstones Not Syncing

Ensure tombstones are being exported:

# Check if tombstone is in JSONL
grep '"id":"bd-xxx"' .beads/issues.jsonl

# Force export
bd export --force
bd sync

Too Many Tombstones

If you have many old tombstones:

# Check tombstone count
bd list --status=tombstone | wc -l

# Prune expired tombstones
bd compact

Design Rationale

Why Inline Tombstones?

The previous deletions.jsonl manifest had issues:

  • Wild poisoning: Stale clone's manifest could delete issues incorrectly
  • Merge inconsistency: Separate file meant separate merge logic
  • Two sources of truth: Issue data and deletion data could diverge

Inline tombstones solve these by:

  • Single source of truth (issues.jsonl)
  • Same merge semantics as regular issues
  • Atomic with issue data
  • Full audit trail preserved

Why TTL-Based Expiration?

  • Bounds storage growth (tombstones eventually pruned)
  • Git history fallback handles edge cases
  • 30-day default handles typical sync scenarios
  • Configurable for teams with longer sync cycles

Why 1-Hour Grace Period?

Clock skew between machines can cause issues:

  • Machine A deletes issue at 10:00 (its clock)
  • Machine B's clock is 30 minutes ahead
  • Without grace period, B might see tombstone as expired immediately

The 1-hour grace period ensures tombstones propagate even with minor clock drift.