gastownhall/beads · error

failed to remove relates-to %s -> %s: %w

Error message

failed to remove relates-to %s -> %s: %w

What it means

runUnrelate removes the relates-to dependency in both directions using store.RemoveDependencyWithOptions with EmitEvent: true (per Decision 004, relates-to links live in the dependencies table and unrelate records history). This wraps a storage error from the first removal, id1 -> id2. Both issues were already verified to exist, so this is a dependency-table write failure, not a missing record.

Source

Thrown at cmd/bd/relate.go:194

	issue2, err = store.GetIssue(ctx, id2)
	if err != nil {
		return fmt.Errorf("failed to get issue %s: %w", id2, err)
	}

	if issue1 == nil {
		return fmt.Errorf("issue not found: %s", id1)
	}
	if issue2 == nil {
		return fmt.Errorf("issue not found: %s", id2)
	}

	// Remove relates-to dependency in both directions
	// Per Decision 004, relates-to links are now stored in dependencies table.
	// bd unrelate is an explicit dependency verb, so it records history
	// (EmitEvent) like bd dep remove; only structural teardown stays silent.
	// Remove id1 -> id2
	if err := store.RemoveDependencyWithOptions(ctx, id1, id2, actor, storage.DependencyRemoveOptions{EmitEvent: true}); err != nil {
		return fmt.Errorf("failed to remove relates-to %s -> %s: %w", id1, id2, err)
	}
	// Remove id2 -> id1 (bidirectional)
	if err := store.RemoveDependencyWithOptions(ctx, id2, id1, actor, storage.DependencyRemoveOptions{EmitEvent: true}); err != nil {
		return fmt.Errorf("failed to remove relates-to %s -> %s: %w", id2, id1, err)
	}

	if jsonOutput {
		result := map[string]interface{}{
			"id1":       id1,
			"id2":       id2,
			"unrelated": true,
		}
		encoder := json.NewEncoder(os.Stdout)
		encoder.SetIndent("", "  ")
		return encoder.Encode(result)
	}

	fmt.Printf("%s Unlinked %s ↔ %s\n", ui.RenderPass("✓"), id1, id2)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the wrapped inner error to identify the storage failure (lock vs constraint vs I/O) and address that cause directly.
  2. If it is a lock, wait for the other bd process / `bd dolt` command to finish and rerun the unrelate; serialize writers.
  3. Run `bd doctor` to check database health; if the dependencies table is inconsistent, follow its repair guidance or restore from backup and re-sync.
  4. Ensure both endpoints use consistent bd versions — upgrade/downgrade so the writer matches the schema that created the dependency rows.
  5. Retry after transient I/O failures; the removal is a single transaction so a partial delete-without-event should not persist.

Example fix

// before
$ bd unrelate bd-1 bd-2 &  bd dolt push &   # concurrent writers
failed to remove relates-to bd-1 -> bd-2: database is locked
// after
$ bd dolt push && bd unrelate bd-1 bd-2     # run storage ops sequentially
Defensive patterns

Strategy: try-catch

Validate before calling

// both issues verified to exist; check storage writability before mutating
if err := store.RunInTransaction(ctx, func(tx storage.Tx) error { return nil }); err != nil {
	return fmt.Errorf("storage not writable: %w", err)
}

Try / catch

err := store.RemoveDependencyWithOptions(ctx, id1, id2, actor, storage.DependencyRemoveOptions{EmitEvent: true})
if err != nil {
	if errors.Is(err, ErrDatabaseLocked) {
		// serialize with other writers, then retry once
		time.Sleep(time.Second)
		err = store.RemoveDependencyWithOptions(ctx, id1, id2, actor, storage.DependencyRemoveOptions{EmitEvent: true})
	}
	if err != nil {
		return fmt.Errorf("failed to remove relates-to %s -> %s: %w", id1, id2, err)
	}
}

Prevention

When it happens

Trigger: store.RemoveDependencyWithOptions(ctx, id1, id2, actor, {EmitEvent:true}) errors: database locked by a concurrent writer, constraint or driver error deleting the dependency row, EmitEvent history write failing, or storage corruption in the dependencies table.

Common situations: Concurrent `bd dep`/sync operations holding a write lock; a half-migrated dependencies table (older bd version created the data); disk I/O failure during the delete-plus-event transaction; corrupted Dolt store after a crash.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/81f4a4dd0f2945a8. Report an issue: GitHub.