Repository: bufbuild/buf
Pull request #945 (2022-02-15 18:08:05)

Update golangci-lint to v1.44.0
This updates `golangic-lint` to `v1.44.0` and addresses some of the issues with the `forcetypeassert` plugin that were previously uncaught (they were caught in the upgrade).

There have been several new linters added in this release:
- [containdctx](https://github.com/sivchari/containedctx)
- [decorder](https://gitlab.com/bosi/decorder)
- [errchkjson](https://github.com/breml/errchkjson)
- [grouper](https://github.com/leonklingele/grouper)
- [maintidx](https://github.com/yagipy/maintidx)

I don't think we want to add any of them - the `errchkjson` one is interesting, but we actually encourage that we check all of our errors so it's not really useful in our case.


bufdev: I'd disagree: `decorder` in particular seems to enforce our specific style guidelines. `containdctx` looks nice, I actually don't like `errchkjson` for the reason you described, and `grouper` looks nice too. The only thing I wouldn't want is `maintidx` probably.

Patch:
diff --git a/.dockerignore b/.dockerignore
index 26e764631..a5d30b3ea 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -13,6 +13,7 @@ private/buf/cmd/buf/workspacetests/other/proto/workspacetest/cache/
 private/bufpkg/bufprotoplugin/cmd/protoc-gen-go-api/protoc-gen-go-api
 private/bufpkg/bufprotoplugin/cmd/protoc-gen-go-apiclient/protoc-gen-go-apiclient
 private/bufpkg/bufprotoplugin/cmd/protoc-gen-go-apiclientgrpc/protoc-gen-go-apiclientgrpc
+private/bufpkg/bufstyle/cmd/bufstyle/bufstyle
 private/bufpkg/buftesting/cache/
 private/pkg/bandeps/cmd/bandeps/bandeps
 private/pkg/git/cmd/git-ls-files-unstaged/git-ls-files-unstaged
diff --git a/.gitignore b/.gitignore
index 2784c1806..a5dbf1be3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,6 +13,7 @@
 /private/bufpkg/bufprotoplugin/cmd/protoc-gen-go-api/protoc-gen-go-api
 /private/bufpkg/bufprotoplugin/cmd/protoc-gen-go-apiclient/protoc-gen-go-apiclient
 /private/bufpkg/bufprotoplugin/cmd/protoc-gen-go-apiclientgrpc/protoc-gen-go-apiclientgrpc
+/private/bufpkg/bufstyle/cmd/bufstyle/bufstyle
 /private/bufpkg/buftesting/cache/
 /private/pkg/bandeps/cmd/bandeps/bandeps
 /private/pkg/git/cmd/git-ls-files-unstaged/git-ls-files-unstaged
diff --git a/.golangci.yml b/.golangci.yml
index 440218721..13db1e0d1 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -33,8 +33,10 @@ linters:
     - asciicheck
     - bidichk
     - bodyclose
+    - containedctx
     - contextcheck
     - deadcode
+    - decorder
     - depguard
     - dogsled
     - errcheck
@@ -50,6 +52,7 @@ linters:
     - gosimple
     - gosec
     - govet
+    - grouper
     - importas
     - ineffassign
     - makezero
@@ -110,3 +113,11 @@ issues:
     - linters:
         - gochecknoinits
       path: private/usage/usage.go
+      # we actually want to embed a context here
+    - linters:
+        - containedctx
+      path: private/bufpkg/bufmodule/bufmoduleprotoparse/path_resolver.go
+      # we actually want to embed a context here
+    - linters:
+        - containedctx
+      path: private/pkg/transport/grpc/grpcclient/client_conn_provider.go
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b1fd52f16..d18ce4f9e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -26,6 +26,7 @@
 - Removed the `buf config ls-lint-rules` command in favor of `buf mod ls-lint-rules`.
 - Removed the `buf config migrate-v1beta1` command in favor of `buf beta migrate-v1beta1`.
 - Add `buf beta decode` command to decode message with provided image source and message type.
+- Disable `--config` flag for workspaces.
 
 ## [v1.0.0-rc12] - 2022-02-01
 
diff --git a/go.sum b/go.sum
index 2819cf1ab..e13ed4883 100644
--- a/go.sum
+++ b/go.sum
@@ -427,6 +427,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
+golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
 golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
diff --git a/make/buf/all.mk b/make/buf/all.mk
index ccf019da6..40186784e 100644
--- a/make/buf/all.mk
+++ b/make/buf/all.mk
@@ -9,6 +9,7 @@ GO_BINS := $(GO_BINS) \
 	private/bufpkg/bufprotoplugin/cmd/protoc-gen-go-api \
 	private/bufpkg/bufprotoplugin/cmd/protoc-gen-go-apiclient \
 	private/bufpkg/bufprotoplugin/cmd/protoc-gen-go-apiclientgrpc \
+	private/bufpkg/bufstyle/cmd/bufstyle \
 	private/pkg/bandeps/cmd/bandeps \
 	private/pkg/git/cmd/git-ls-files-unstaged \
 	private/pkg/storage/cmd/ddiff \
@@ -50,6 +51,13 @@ include make/go/buf.mk
 
 installtest:: $(PROTOC) $(PROTOC_GEN_GO)
 
+.PHONY: bufstyle
+bufstyle: installbufstyle
+	@echo bufstyle NON_GEN_GOPKGS
+	@bufstyle $(shell go list $(GOPKGS) | grep -v \/gen\/)
+
+postlint:: bufstyle
+
 .PHONY: bandeps
 bandeps: installbandeps
 	bandeps -f data/bandeps/bandeps.yaml
diff --git a/make/go/base.mk b/make/go/base.mk
index ddb4cbddf..c889d969e 100644
--- a/make/go/base.mk
+++ b/make/go/base.mk
@@ -120,7 +120,7 @@ envrestore:
 	cp -R "$(ENV_BACKUP_DIR)" "$(ENV_DIR)"
 
 # All variables set in env.sh by this target need to also be exported
-# above in the Runtime ALL section.
+# above in the Runtime ALL section, except for MAKEFLAGS.
 .PHONY: direnv
 direnv:
 	@mkdir -p $(CACHE_ENV)
@@ -134,6 +134,7 @@ direnv:
 	@echo 'export GOMODCACHE="$(GOPATH)/pkg/mod"' >> $(CACHE_ENV)/env.sh
 	@echo 'export PATH="$(EXTRAPATH):$${PATH}"' >> $(CACHE_ENV)/env.sh
 	@echo 'export DOCKER_BUILDKIT=1' >> $(CACHE_ENV)/env.sh
+	@echo 'export MAKEFLAGS="-j $$(exec 2>/dev/null; nproc || getconf _NPROCESSORS_ONLN || sysctl -n hw.ncpu || echo 1)"' >> $(CACHE_ENV)/env.sh
 ifneq ($(DOCKER_DEFAULT_PLATFORM),)
 	@echo 'export DOCKER_DEFAULT_PLATFORM="$(DOCKER_DEFAULT_PLATFORM)"' >> $(CACHE_ENV)/env.sh
 endif
diff --git a/make/go/dep_golangci_lint.mk b/make/go/dep_golangci_lint.mk
index 6e07b6b23..22ccedce0 100644
--- a/make/go/dep_golangci_lint.mk
+++ b/make/go/dep_golangci_lint.mk
@@ -7,9 +7,9 @@ $(call _assert_var,CACHE_VERSIONS)
 $(call _assert_var,CACHE_BIN)
 
 # Settable
-# https://github.com/golangci/golangci-lint/releases 20211103 checked 20211112
+# https://github.com/golangci/golangci-lint/releases 20220125 checked 20220215
 # Check for new linters and add to .golangci.yml (even if commented out) when upgrading
-GOLANGCI_LINT_VERSION ?= v1.43.0
+GOLANGCI_LINT_VERSION ?= v1.44.0
 
 GOLANGCI_LINT := $(CACHE_VERSIONS)/golangci-lint/$(GOLANGCI_LINT_VERSION)
 $(GOLANGCI_LINT):
diff --git a/make/go/license_header.mk b/make/go/license_header.mk
index ef2c2bb42..7c917dddf 100644
--- a/make/go/license_header.mk
+++ b/make/go/license_header.mk
@@ -17,15 +17,11 @@ $(call _assert_var,LICENSE_HEADER_IGNORES)
 
 .PHONY: licenseheader
 licenseheader: $(LICENSE_HEADER) $(GIT_LS_FILES_UNSTAGED)
-	@echo license-header \
-		--license-type "$(LICENSE_HEADER_LICENSE_TYPE)" \
-		--copyright-holder "$(LICENSE_HEADER_COPYRIGHT_HOLDER)" \
-		--year-range "$(LICENSE_HEADER_YEAR_RANGE)" \
-		ALL_FILES
-	@license-header \
-		--license-type "$(LICENSE_HEADER_LICENSE_TYPE)" \
-		--copyright-holder "$(LICENSE_HEADER_COPYRIGHT_HOLDER)" \
-		--year-range "$(LICENSE_HEADER_YEAR_RANGE)" \
-		$(shell git-ls-files-unstaged | grep -v $(patsubst %,-e %,$(sort $(LICENSE_HEADER_IGNORES))))
+	git-ls-files-unstaged | \
+		grep -v $(patsubst %,-e %,$(sort $(LICENSE_HEADER_IGNORES))) | \
+		xargs license-header \
+			--license-type "$(LICENSE_HEADER_LICENSE_TYPE)" \
+			--copyright-holder "$(LICENSE_HEADER_COPYRIGHT_HOLDER)" \
+			--year-range "$(LICENSE_HEADER_YEAR_RANGE)"
 
 licensegenerate:: licenseheader
diff --git a/private/buf/bufwork/workspace_builder.go b/private/buf/bufwork/workspace_builder.go
index 3a227edba..6950811b2 100644
--- a/private/buf/bufwork/workspace_builder.go
+++ b/private/buf/bufwork/workspace_builder.go
@@ -56,6 +56,9 @@ func (w *workspaceBuilder) BuildWorkspace(
 	if workspaceConfig == nil {
 		return nil, errors.New("received a nil workspace config")
 	}
+	if configOverride != "" {
+		return nil, errors.New("the --config flag is not compatible with workspaces")
+	}
 	// We know that if the file is actually buf.work for legacy reasons, this will be wrong,
 	// but we accept that as this shouldn't happen often anymore and this is just
 	// used for error messages.
diff --git a/private/buf/cmd/buf/command/beta/decode/decode.go b/private/buf/cmd/buf/command/beta/decode/decode.go
index 67d4b5d28..1beb89b45 100644
--- a/private/buf/cmd/buf/command/beta/decode/decode.go
+++ b/private/buf/cmd/buf/command/beta/decode/decode.go
@@ -44,10 +44,10 @@ func NewCommand(
 ) *appcmd.Command {
 	flags := newFlags()
 	return &appcmd.Command{
-		Use:   name,
-		Short: "Decode binary serialized message with a source reference.",
-		Long: `The first argument is the source that defines the serialized message (e.g. buf.build/acme/weather).
-If no argument is specified, the type provided must be a fully-qualified path to the type (e.g. buf.build/acme/weather#acme.weather.v1.Units).`,
+		Use:   name + " <source>",
+		Short: "Use a source reference to decode a binary serialized message supplied through stdin.",
+		Long: `The first argument is the source that defines the serialized message (like buf.build/acme/weather).
+Alternatively, you can omit the source and specify a fully qualified path for the type using the --type option (like buf.build/acme/weather#acme.weather.v1.Units).`,
 		Args: cobra.MaximumNArgs(1),
 		Run: builder.NewRunFunc(
 			func(ctx context.Context, container appflag.Container) error {
@@ -75,7 +75,7 @@ func (f *flags) Bind(flagSet *pflag.FlagSet) {
 		errorFormatFlagName,
 		"text",
 		fmt.Sprintf(
-			"The format for build errors, printed to stderr. Must be one of %s.",
+			"The format for build errors printed to stderr. Must be one of %s.",
 			stringutil.SliceToString(bufanalysis.AllFormatStrings),
 		),
 	)
@@ -83,8 +83,8 @@ func (f *flags) Bind(flagSet *pflag.FlagSet) {
 		&f.Type,
 		typeFlagName,
 		"",
-		`The full type name of the serialized message (e.g. acme.weather.v1.Units)
-Alternatively, this can be a fully-qualified path to the type (e.g. buf.build/acme/weather#acme.weather.v1.Units) without providing the source`,
+		`The full type name of the serialized message (like acme.weather.v1.Units).
+Alternatively, this can be a fully qualified path to the type without providing the source (like buf.build/acme/weather#acme.weather.v1.Units).`,
 	)
 	flagSet.StringVarP(
 		&f.Output,
@@ -93,7 +93,7 @@ Alternatively, this can be a fully-qualified path to the type (e.g. buf.build/ac
 		"-",
 		// TODO: If we ever support other formats (e.g. prototext), we will need
 		// to build a buffetch.ProtoEncodingRefParser.
-		`The location to write the decoded result to. This is always JSON for now.`,
+		`The location to write the decoded result to. Output is currently only in JSON form.`,
 	)
 }
 
diff --git a/private/bufpkg/bufstyle/bufstyle.go b/private/bufpkg/bufstyle/bufstyle.go
new file mode 100644
index 000000000..652f8e407
--- /dev/null
+++ b/private/bufpkg/bufstyle/bufstyle.go
@@ -0,0 +1,69 @@
+// Copyright 2020-2022 Buf Technologies, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package bufstyle defines lint analyzers that help enforce Buf's Go code standards.
+package bufstyle
+
+import (
+	"go/token"
+	"path/filepath"
+	"strings"
+
+	"golang.org/x/tools/go/analysis"
+)
+
+var (
+	// Analyzers are all the analyzers implemented to help enforce Buf's style guide.
+	Analyzers = []*analysis.Analyzer{
+		packagefilenameAnalyzer,
+	}
+
+	packagefilenameAnalyzer = &analysis.Analyzer{
+		Name: "packagefilename",
+		Doc:  "Verifies that every package has a file with the same name as the package.",
+		Run:  packagefilenameRun,
+	}
+)
+
+// packagefilenameRun is run once per package.
+func packagefilenameRun(pass *analysis.Pass) (interface{}, error) {
+	if len(pass.Files) == 0 {
+		// Nothing to do. We can't report the error anywhere because
+		// this package doesn't have any files.
+		return nil, nil
+	}
+	packageName := pass.Pkg.Name()
+	if strings.HasSuffix(packageName, "_test") {
+		// Ignore test packages.
+		return nil, nil
+	}
+	var found bool
+	pass.Fset.Iterate(
+		func(file *token.File) bool {
+			filename := filepath.Base(file.Name())
+			if strings.TrimSuffix(filename, ".go") == packageName {
+				found = true
+				return false
+			}
+			return true
+		},
+	)
+	if !found {
+		// The package is guaranteed to have at least one
+		// file with a package declaration, so we report the failure there.
+		// We checked that len(pass.Files) > 0 above.
+		pass.Reportf(pass.Files[0].Package, "Package %q does not have a %s.go", packageName, packageName)
+	}
+	return nil, nil
+}
diff --git a/private/bufpkg/bufstyle/cmd/bufstyle/main.go b/private/bufpkg/bufstyle/cmd/bufstyle/main.go
new file mode 100644
index 000000000..f4b8ed80e
--- /dev/null
+++ b/private/bufpkg/bufstyle/cmd/bufstyle/main.go
@@ -0,0 +1,24 @@
+// Copyright 2020-2022 Buf Technologies, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+	"github.com/bufbuild/buf/private/bufpkg/bufstyle"
+	"golang.org/x/tools/go/analysis/multichecker"
+)
+
+func main() {
+	multichecker.Main(bufstyle.Analyzers...)
+}
diff --git a/private/bufpkg/bufstyle/cmd/bufstyle/usage.gen.go b/private/bufpkg/bufstyle/cmd/bufstyle/usage.gen.go
new file mode 100644
index 000000000..cad4dba28
--- /dev/null
+++ b/private/bufpkg/bufstyle/cmd/bufstyle/usage.gen.go
@@ -0,0 +1,19 @@
+// Copyright 2020-2022 Buf Technologies, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Generated. DO NOT EDIT.
+
+package main
+
+import _ "github.com/bufbuild/buf/private/usage"
diff --git a/private/bufpkg/bufstyle/usage.gen.go b/private/bufpkg/bufstyle/usage.gen.go
new file mode 100644
index 000000000..f6fc0e5e3
--- /dev/null
+++ b/private/bufpkg/bufstyle/usage.gen.go
@@ -0,0 +1,19 @@
+// Copyright 2020-2022 Buf Technologies, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Generated. DO NOT EDIT.
+
+package bufstyle
+
+import _ "github.com/bufbuild/buf/private/usage"
diff --git a/private/pkg/rpc/headers.go b/private/pkg/rpc/headers.go
index e5d2112a7..327280f83 100644
--- a/private/pkg/rpc/headers.go
+++ b/private/pkg/rpc/headers.go
@@ -31,7 +31,12 @@ type incomingHeadersContextKey struct{}
 // If the key is unset, this returns the empty string.
 func GetIncomingHeader(ctx context.Context, key string) string {
 	if contextValue := ctx.Value(incomingHeadersContextKey{}); contextValue != nil {
-		return contextValue.(map[string]string)[normalizeHeaderKey(key)]
+		value, ok := contextValue.(map[string]string)[normalizeHeaderKey(key)]
+		if !ok {
+			// Unreachable
+			return ""
+		}
+		return value
 	}
 	return ""
 }
@@ -45,7 +50,12 @@ func GetIncomingHeader(ctx context.Context, key string) string {
 // If the key is unset, this returns the empty string.
 func GetOutgoingHeader(ctx context.Context, key string) string {
 	if contextValue := ctx.Value(outgoingHeadersContextKey{}); contextValue != nil {
-		return contextValue.(map[string]string)[normalizeHeaderKey(key)]
+		value, ok := contextValue.(map[string]string)[normalizeHeaderKey(key)]
+		if !ok {
+			// Unreachable
+			return ""
+		}
+		return value
 	}
 	return ""
 }
diff --git a/private/pkg/rpc/rpcauth/rpcauth.go b/private/pkg/rpc/rpcauth/rpcauth.go
index 605fb1a92..509e05d44 100644
--- a/private/pkg/rpc/rpcauth/rpcauth.go
+++ b/private/pkg/rpc/rpcauth/rpcauth.go
@@ -49,7 +49,8 @@ func GetUser(ctx context.Context) (*User, bool) {
 	}
 	// This is the only package where we can set this context key, so
 	// this is guaranteed to be of this type if it exists.
-	return userValue.(*User), true
+	user, ok := userValue.(*User)
+	return user, ok
 }
 
 // WithToken adds the token to the context via a header.


Test patch:
diff --git a/private/buf/cmd/buf/buf_test.go b/private/buf/cmd/buf/buf_test.go
index 8d71fbfa2..b06fe0e19 100644
--- a/private/buf/cmd/buf/buf_test.go
+++ b/private/buf/cmd/buf/buf_test.go
@@ -244,45 +244,32 @@ func TestFail7(t *testing.T) {
 	testRunStdout(
 		t,
 		nil,
-		bufcli.ExitCodeFileAnnotation,
-		filepath.FromSlash(`testdata/fail/buf/buf.proto:3:1:Files with package "other" must be within a directory "other" relative to root but were in directory "fail/buf".
-        testdata/fail/buf/buf.proto:6:9:Field name "oneTwo" should be lower_snake_case, such as "one_two".`),
+		1,
+		``,
 		"lint",
-		"--path",
 		filepath.Join("testdata", "fail", "buf", "buf.proto"),
-		filepath.Join("testdata"),
-		"--config",
-		`{"version":"v1beta1","lint":{"use":["BASIC"]}}`,
+		"--input-config",
+		`{"version":"v1","lint":{"use":["BASIC"]}}`,
 	)
-	testRunStdout(
+	testRunStdoutStderr(
 		t,
 		nil,
-		bufcli.ExitCodeFileAnnotation,
-		filepath.FromSlash(`testdata/fail/buf/buf.proto:3:1:Files with package "other" must be within a directory "other" relative to root but were in directory "fail/buf".
-        testdata/fail/buf/buf.proto:6:9:Field name "oneTwo" should be lower_snake_case, such as "one_two".`),
+		1,
+		"", // stdout should be empty
+		"Failure: the --config flag is not compatible with workspaces",
 		"lint",
 		"--path",
 		filepath.Join("testdata", "fail", "buf", "buf.proto"),
 		filepath.Join("testdata"),
 		"--config",
-		`{"version":"v1","lint":{"use":["BASIC"]}}`,
+		`{"version":"v1beta1","lint":{"use":["BASIC"]}}`,
 	)
-	testRunStdout(
+	testRunStdoutStderr(
 		t,
 		nil,
 		1,
-		``,
-		"lint",
-		filepath.Join("testdata", "fail", "buf", "buf.proto"),
-		"--input-config",
-		`{"version":"v1","lint":{"use":["BASIC"]}}`,
-	)
-	testRunStdout(
-		t,
-		nil,
-		bufcli.ExitCodeFileAnnotation,
-		filepath.FromSlash(`testdata/fail/buf/buf.proto:3:1:Files with package "other" must be within a directory "other" relative to root but were in directory "buf".
-        testdata/fail/buf/buf.proto:6:9:Field name "oneTwo" should be lower_snake_case, such as "one_two".`),
+		"", // stdout should be empty
+		"Failure: the --config flag is not compatible with workspaces",
 		"lint",
 		filepath.Join("testdata", "fail", "buf", "buf.proto"),
 		"--config",
diff --git a/private/buf/cmd/buf/workspace_test.go b/private/buf/cmd/buf/workspace_test.go
index 8cf47e735..4c9e0479a 100644
--- a/private/buf/cmd/buf/workspace_test.go
+++ b/private/buf/cmd/buf/workspace_test.go
@@ -161,12 +161,12 @@ func TestWorkspaceDir(t *testing.T) {
 			"--against",
 			filepath.Join("testdata", "workspace", "success", baseDirPath),
 		)
-		testRunStdout(
+		testRunStdoutStderr(
 			t,
 			nil,
-			bufcli.ExitCodeFileAnnotation,
-			filepath.FromSlash(`testdata/workspace/success/`+baseDirPath+`/other/proto/request.proto:3:1:Files with package "request" must be within a directory "request" relative to root but were in directory ".".
-		    testdata/workspace/success/`+baseDirPath+`/proto/rpc.proto:3:1:Files with package "example" must be within a directory "example" relative to root but were in directory ".".`),
+			1,
+			"", // stdout should be empty
+			"Failure: the --config flag is not compatible with workspaces",
 			"lint",
 			filepath.Join("testdata", "workspace", "success", baseDirPath),
 			"--config",
@@ -194,23 +194,12 @@ func TestWorkspaceDir(t *testing.T) {
 			"--path",
 			filepath.Join("testdata", "workspace", "success", baseDirPath, "other", "proto", "request.proto"),
 		)
-		testRunStdout(
+		testRunStdoutStderr(
 			t,
 			nil,
-			bufcli.ExitCodeFileAnnotation,
-			filepath.FromSlash(`testdata/workspace/success/`+baseDirPath+`/proto/rpc.proto:3:1:Files with package "example" must be within a directory "example" relative to root but were in directory ".".`),
-			"lint",
-			filepath.Join("testdata", "workspace", "success", baseDirPath),
-			"--config",
-			`{"version":"v1","lint": {"use": ["PACKAGE_DIRECTORY_MATCH"]}}`,
-			"--path",
-			filepath.Join("testdata", "workspace", "success", baseDirPath, "proto", "rpc.proto"),
-		)
-		testRunStdout(
-			t,
-			nil,
-			bufcli.ExitCodeFileAnnotation,
-			filepath.FromSlash(`testdata/workspace/success/`+baseDirPath+`/other/proto/request.proto:3:1:Files with package "request" must be within a directory "request" relative to root but were in directory ".".`),
+			1,
+			"", // stdout should be empty
+			"Failure: the --config flag is not compatible with workspaces",
 			"lint",
 			filepath.Join("testdata", "workspace", "success", baseDirPath),
 			"--config",
@@ -218,32 +207,6 @@ func TestWorkspaceDir(t *testing.T) {
 			"--path",
 			filepath.Join("testdata", "workspace", "success", baseDirPath, "other", "proto", "request.proto"),
 		)
-		testRunStdout(
-			t,
-			nil,
-			bufcli.ExitCodeFileAnnotation,
-			filepath.FromSlash(`testdata/workspace/success/`+baseDirPath+`/other/proto/request.proto:3:1:Files with package "request" must be within a directory "request" relative to root but were in directory ".".`),
-			"lint",
-			filepath.Join("testdata", "workspace", "success", baseDirPath),
-			"--config",
-			`{"version":"v1","lint": {"use": ["PACKAGE_DIRECTORY_MATCH"]}}`,
-			"--path",
-			filepath.Join(wd, "testdata", "workspace", "success", baseDirPath, "other", "proto", "request.proto"),
-		)
-		testRunStdout(
-			t,
-			nil,
-			bufcli.ExitCodeFileAnnotation,
-			filepath.FromSlash(fmt.Sprintf(`%s/testdata/workspace/success/`+baseDirPath+`/other/proto/request.proto:3:1:Files with package "request" must be within a directory "request" relative to root but were in directory ".".`,
-				wd,
-			)),
-			"lint",
-			filepath.Join(wd, "testdata", "workspace", "success", baseDirPath),
-			"--config",
-			`{"version":"v1","lint": {"use": ["PACKAGE_DIRECTORY_MATCH"]}}`,
-			"--path",
-			filepath.Join(wd, "testdata", "workspace", "success", baseDirPath, "other", "proto", "request.proto"),
-		)
 	}
 }
 
diff --git a/private/buf/cmd/buf/workspacetests/other/proto/workspacetest/workspacetest.go b/private/buf/cmd/buf/workspacetests/other/proto/workspacetest/workspacetest.go
new file mode 100644
index 000000000..d860b6503
--- /dev/null
+++ b/private/buf/cmd/buf/workspacetests/other/proto/workspacetest/workspacetest.go
@@ -0,0 +1,15 @@
+// Copyright 2020-2022 Buf Technologies, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package workspacetest