Files
beads/docs/DELETIONS.md
Steve Yegge d0694e6171 docs: update documentation for CLI consolidation (bd-bxqv)
Update docs to reflect CLI command restructuring:
- migrate-tombstones → migrate tombstones
- bd quickstart is now hidden, point to docs/QUICKSTART.md
- Add CLI consolidation section to CHANGELOG.md
- Remove quickstart from command tables

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-27 16:21:08 -08:00

7.1 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 admin 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 admin 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 admin 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.

Wisps: Intentional Tombstone Bypass

Wisps (ephemeral issues created by bd mol wisp) are intentionally excluded from tombstone tracking.

Why Wisps Don't Need Tombstones

Tombstones exist to prevent resurrection during sync. Wisps don't sync:

Property Regular Issues Wisps
Exported to JSONL Yes No
Synced to other clones Yes No
Can resurrect Yes No
Tombstone on delete Yes No (hard delete)

Since wisps never leave the local SQLite database, they cannot resurrect from remote clones. Creating tombstones for them would be unnecessary overhead.

How Wisp Deletion Works

When bd mol squash compresses wisps into a digest:

  1. The digest issue is created (permanent, syncs normally)
  2. Wisp children are hard-deleted via DeleteIssue()
  3. No tombstones are created
  4. The wisps simply disappear from local SQLite

This is intentional, not a bug. See ARCHITECTURE.md for the full design rationale.

If You Need Wisp History

Wisps are stored in the main database with Wisp=true flag and are not exported to JSONL. They exist in local SQLite until garbage collected or squashed. Future enhancements may include:

  • Configurable wisp retention policies
  • Automatic staleness detection based on dependency graph pressure