Skip to content

rules_buf generates invalid targets from hidden directories #133

@dspencer12

Description

@dspencer12

Bug

The getProtoImportPaths function in gazelle/buf/generate.go uses fs.WalkDir to discover .proto files under the module root, but does not skip hidden directories (names starting with .). This causes it to discover proto files in directories like .git/, .claude/, or other tool-managed hidden directories, generating buf_breaking_test targets that reference nonexistent packages and break bazel build //....

Gazelle's own directory walker skips hidden directories by default, so the buf extension's behavior is inconsistent with the rest of Gazelle.

Repro

  1. Have a buf.yaml v2 config with modules: [{path: "."}] and # gazelle:buf_breaking_against set to module mode
  2. Have any hidden directory under the workspace root that contains .proto files (e.g. .claude/worktrees/ from Claude Code, or any git worktree under a dotdir)
  3. Run bazel run //:gazelle

The generated buf_breaking_test in the root BUILD.bazel will include targets like:

buf_breaking_test(
    name = "buf_breaking",
    targets = [
        # ... legitimate targets ...
        "@buf_deps//.claude/worktrees/some-worktree/apps/my-app/proto:proto_proto",
        # ... hundreds more from each worktree ...
    ],
)

These @buf_deps// targets don't exist (the @buf_deps workspace mirror correctly skips dotdirs), so bazel build //... fails.

Root cause

In gazelle/buf/generate.go:

func getProtoImportPaths(config *Config, moduleRoot string) []string {
	var targets []string
	fs.WalkDir(
		os.DirFS(moduleRoot),
		".",
		func(path string, dirEntry fs.DirEntry, err error) error {
			if dirEntry.IsDir() {
				return nil  // enters ALL directories, including hidden ones
			}
			// ...
		},
	)
	return targets
}

When dirEntry.IsDir() is true, the function returns nil (continue walking) instead of returning fs.SkipDir for hidden directories.

Additionally, the function does not respect the includes field from buf.yaml v2 module config — it walks the entire module root regardless. The excludes field cannot be used as a workaround because buf v2 validates that exclude paths must be within include paths.

Suggested fix

func(path string, dirEntry fs.DirEntry, err error) error {
    if dirEntry.IsDir() {
        // Skip hidden directories (e.g. .git, .claude) to match
        // Gazelle's own directory-walking behavior.
        if path != "." && strings.HasPrefix(dirEntry.Name(), ".") {
            return fs.SkipDir
        }
        return nil
    }

A more complete fix would also filter by includes when present in the v2 module config, but skipping hidden directories addresses the most common case and matches Gazelle's conventions.

Workaround

We're currently applying this as a patch via archive_override in MODULE.bazel:

archive_override(
    module_name = "rules_buf",
    patch_strip = 1,
    patches = ["//third-party/patches:rules_buf_skip_hidden_dirs.patch"],
    # ...
)

Environment

  • rules_buf 0.5.2
  • Bazel 7.x with bzlmod
  • buf.yaml v2 with module mode breaking checks

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions