gastownhall/beads · error

comparing sidecars %s and %s: %w

Error message

comparing sidecars %s and %s: %w

What it means

This error wraps a failure from filesEqual, which reads both sidecar files fully with os.ReadFile and byte-compares them during collision validation. It means one or both files could not be read (permissions, missing after the earlier exists-check, or I/O error). Validation aborts before any migration is applied.

Source

Thrown at cmd/bd/migrate_hooks_apply.go:368

		sourceExists, err := pathExists(op.SourcePath)
		if err != nil {
			return fmt.Errorf("checking source sidecar %s: %w", op.SourcePath, err)
		}
		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)
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check the wrapped error and run `ls -la` on both paths to see what they actually are (file vs directory, permissions).
  2. If either path is a directory, remove or relocate it manually and re-run.
  3. Grant read permission on both files: chmod u+r on the source and destination.
  4. Re-run the migration; transient races usually resolve on a second run.

Example fix

// before
-rw------- destination-sidecar  (unreadable by current user)
// after
$ chmod u+r /path/to/destination-sidecar
$ bd migrate
Defensive patterns

Strategy: validation

Validate before calling

for _, p := range []string{op.SourcePath, op.DestinationPath} {
	fi, err := os.Stat(p)
	if err != nil || fi.IsDir() {
		return fmt.Errorf("%s is not a readable regular file", p)
	}
	f, err := os.Open(p)
	if err != nil {
		return fmt.Errorf("%s not readable: %w", p, err)
	}
	f.Close()
}

Try / catch

if err := validateRetireCollisionPolicy(ops); err != nil {
	var pe *fs.PathError
	if errors.As(err, &pe) {
		log.Printf("unreadable sidecar %s: %v", pe.Path, pe.Err)
	}
	return err
}

Prevention

When it happens

Trigger: During `bd migrate` collision validation, both source and destination sidecar paths exist per os.Stat, but os.ReadFile on either path fails — e.g. file permissions changed between the stat and read, the path is a directory, or a read I/O error occurs.

Common situations: A file deleted or chmod'ed between the exists check and the compare (race with another process), stat succeeding on a directory instead of a file, or a file unreadable by the current user despite directory access.

Related errors


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