diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0303ad0..e48e564 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ flake8-pyi uses Calendar Versioning (CalVer).
avoid false positives in stub files. This monkey patch has been removed.
Instead, we now recommend to disable F821 when running flake8 on stub files.
* Remove the now unnecessary `--no-pyi-aware-file-checker` option.
+* Y034 no longer triggers in protocol class definitions.
### New Error Codes
diff --git a/ERRORCODES.md b/ERRORCODES.md
index db2565c..0c8f984 100644
--- a/ERRORCODES.md
+++ b/ERRORCODES.md
@@ -47,7 +47,7 @@ The following warnings are currently emitted by default:
| Y031 | `TypedDict`s should use class-based syntax instead of assignment-based syntax wherever possible. (In situations where this is not possible, such as if a field is a Python keyword or an invalid identifier, this error will not be emitted.) | Style
| Y032 | The second argument of an `__eq__` or `__ne__` method should usually be annotated with `object` rather than `Any`. | Correctness
| Y033 | Do not use type comments (e.g. `x = ... # type: int`) in stubs. Always use annotations instead (e.g. `x: int`). | Style
-| Y034 | Y034 detects common errors where certain methods are annotated as having a fixed return type, despite returning `self` at runtime. Such methods should be annotated with `typing_extensions.Self`. This check looks for:
**1.** Any in-place BinOp dunder methods (`__iadd__`, `__ior__`, etc.) that do not return `Self`.
**2.** `__new__`, `__enter__` and `__aenter__` methods that return the class's name unparameterised.
**3.** `__iter__` methods that return `Iterator`, even if the class inherits directly from `Iterator`.
**4.** `__aiter__` methods that return `AsyncIterator`, even if the class inherits directly from `AsyncIterator`.
This check excludes methods decorated with `@overload` or `@abstractmethod`. | Correctness
+| Y034 | Y034 detects common errors where certain methods are annotated as having a fixed return type, despite returning `self` at runtime. Such methods should be annotated with `typing_extensions.Self`. This check looks for:
**1.** Any in-place BinOp dunder methods (`__iadd__`, `__ior__`, etc.) that do not return `Self`.
**2.** `__new__`, `__enter__` and `__aenter__` methods that return the class's name unparameterised.
**3.** `__iter__` methods that return `Iterator`, even if the class inherits directly from `Iterator`.
**4.** `__aiter__` methods that return `AsyncIterator`, even if the class inherits directly from `AsyncIterator`.
Note that this check excludes methods decorated with `@overload` or `@abstractmethod`, and those in the bodies of protocol classes. | Correctness
| Y035 | `__all__`, `__match_args__` and `__slots__` in a stub file should always have values, as these special variables in a `.pyi` file have identical semantics in a stub as at runtime. E.g. write `__all__ = ["foo", "bar"]` instead of `__all__: list[str]`. | Correctness
| Y036 | Y036 detects common errors in `__exit__` and `__aexit__` methods. For example, the first argument in an `__exit__` method should either be annotated with `object`, `_typeshed.Unused` (a special alias for `object`) or `type[BaseException] \| None`. | Correctness
| Y037 | Use PEP 604 syntax instead of `typing(_extensions).Union` and `typing(_extensions).Optional`. E.g. use `str \| int` instead of `Union[str, int]`, and use `str \| None` instead of `Optional[str]`. | Style
diff --git a/flake8_pyi/visitor.py b/flake8_pyi/visitor.py
index 059f846..5e1fca7 100644
--- a/flake8_pyi/visitor.py
+++ b/flake8_pyi/visitor.py
@@ -366,6 +366,10 @@ def _has_bad_hardcoded_returns(
if class_ctx.is_metaclass:
return False
+ # Return false if in a protocol (class inheriting from typing.Protocol); see #539
+ if class_ctx.is_protocol_class:
+ return False
+
# Much too complex for our purposes to worry
# about overloaded functions or abstractmethods
if any(
diff --git a/tests/classdefs.pyi b/tests/classdefs.pyi
index 29e640d..2c225de 100644
--- a/tests/classdefs.pyi
+++ b/tests/classdefs.pyi
@@ -89,6 +89,9 @@ class InvalidButPluginDoesNotCrash:
def __enter__() -> InvalidButPluginDoesNotCrash: ...
async def __aenter__() -> InvalidButPluginDoesNotCrash: ...
+class ProtocolsAreExemptedFromY034(typing.Protocol):
+ def __iadd__(self, other: Self, /) -> object: ...
+
class BadIterator1(Iterator[int]):
def __iter__(self) -> Iterator[int]: ... # Y034 "__iter__" methods in classes like "BadIterator1" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator1.__iter__", e.g. "def __iter__(self) -> Self: ..."