gastownhall/beads · error

artifact collision for %s: %s already exists with different

Error message

artifact collision for %s: %s already exists with different content

What it means

This is the pre-apply collision abort: the destination sidecar already exists AND its content differs from the source sidecar, so blindly retiring would overwrite user-modified content. validateRetireCollisionPolicy fails fast before any operation is applied, leaving the filesystem untouched.

Source

Thrown at cmd/bd/migrate_hooks_apply.go:371

		}
		if !sourceExists {
			continue
		}

		destinationExists, err := pathExists(op.DestinationPath)
		if err != nil {
			return fmt.Errorf("checking destination sidecar %s: %w", op.DestinationPath, err)
		}
		if !destinationExists {
			continue
		}

		equal, err := filesEqual(op.SourcePath, op.DestinationPath)
		if err != nil {
			return fmt.Errorf("comparing sidecars %s and %s: %w", op.SourcePath, op.DestinationPath, err)
		}
		if !equal {
			return fmt.Errorf(
				"artifact collision for %s: %s already exists with different content",
				op.SourcePath,
				op.DestinationPath,
			)
		}
	}

	return nil
}

func retireHookSidecar(op hookMigrationRetireOp) (string, error) {
	sourceExists, err := pathExists(op.SourcePath)
	if err != nil {
		return "", fmt.Errorf("checking sidecar %s: %w", op.SourcePath, err)
	}
	if !sourceExists {
		return "", nil
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Diff the two files to see what differs: diff <source> <destination>.
  2. If the destination content is obsolete, delete or rename it and re-run `bd migrate`.
  3. If the source content is what you want, back up the destination, remove it, and re-run (the source will be renamed over).
  4. Merge any intentional customizations into the new source sidecar before re-running.

Example fix

// before
$ diff ~/.bd/hooks/old.sh ~/.bd/hooks/new.sh   # differs
// after
$ mv ~/.bd/hooks/new.sh ~/.bd/hooks/new.sh.bak
$ bd migrate   # completes; source content wins
Defensive patterns

Strategy: validation

Validate before calling

if same, _ := filesEqual(src, dst); !same {
	fmt.Printf("collision: %s differs from %s; back up or merge first\n", src, dst)
	os.Exit(1)
}

Try / catch

if err := validateRetireCollisionPolicy(ops); err != nil {
	if strings.Contains(err.Error(), "artifact collision") {
		// prompt user to diff/backup the destination, then retry
		return handleCollision(err)
	}
	return err
}

Prevention

When it happens

Trigger: Running `bd migrate` when a retire op's destination path exists and filesEqual returns false — i.e. the destination sidecar was customized or written by a different bd version than the source.

Common situations: User hand-edited the destination hook sidecar after an earlier migration; two bd versions with different sidecar templates both installed; a partial previous migration left a stale destination file.

Related errors


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