flake8-bugbear holds the lints too opinionated for flake8 itself
A plugin for Flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle.
At a glance
- What is it?
- flake8-bugbear is a plugin for flake8 that carries the warnings its authors believe do not belong in pyflakes or pycodestyle, from bare except clauses to mutable default arguments to a rule against asserting on Exception. The plugin exists because those lints are very opinionated and have no PEP or standard behind them, and it is installed with a single pip command and picked up automatically by flake8.
- Who is it for?
- flake8-bugbear is worth adding to any Python project that runs flake8, because the rules it carries are the ones a reviewer would otherwise have to remember, and because each one is documented with the reason and the alternative, so a team can argue with a rule instead of silently disabling it.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 7, 2026, and from our analysis. They are not legal advice.
Star history
- PyCQA/flake8-bugbear1,126 ★ · +5
| Repository | 2026-10-21 | 2026-11-07 |
|---|---|---|
| PyCQA/flake8-bugbear | 1.1k | 1.1k |
5 Stars in the last 17 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
The reason these lints are not in flake8
The project's own explanation of its existence is the most interesting part of the README. It is felt that these lints don't belong in the main Python tools as they are very opinionated and do not have a PEP or standard behind them. Due to flake8 being designed to be extensible, the original creators of these lints believed that a plugin was the best route, and this has resulted in better development velocity for contributors and adaptive deployment for flake8 users. That is a careful argument rather than an emotional one, and it explains the shape of the project. A check that catches a real bug but is not what everybody means by a Python program belongs outside the tool everybody has to accept, and the plugin route means a team can take the checks it wants. The audience is a Python team that has decided flake8 is the right linter and wants the extra rules without changing anybody's mind about them.
Installing it, and checking that flake8 noticed
The install is one command:
pip install flake8-bugbearThere is nothing to enable in a configuration file, because the plugin registers itself through the flake8 extension entry point, and the README's way of confirming it is to ask flake8 what it is running:
$ flake8 --version
3.5.0 (assertive: 1.0.1, flake8-bugbear: 18.2.0, flake8-comprehensions: 1.4.1, mccabe: 0.6.1, pycodestyle: 2.3.1, pyflakes: 1.6.0) CPython 3.7.0 on DarwinThe listing is worth understanding as a piece of output, because the versions in the parentheses are the versions of the plugins flake8 has loaded, and the numbers are not the pip versions. A checker named assertive appears in that list, and mccabe is the complexity checker that flake8 includes by default. The packaging metadata shows the same shape, with an entry point declared as flake8.extension and a mapping of B to bugbear:BugBearChecker, which is how the B prefixed codes are registered. The runtime dependencies are just flake8 7.2.0 or newer and attrs 22.2.0 or newer, and the whole implementation lives in a single module, bugbear.py, which is declared as a py-module in the packaging configuration rather than as a package.
The B codes, from bare except to mutable defaults
Every warning has a code and a paragraph, and the early ones are the ones a Python reviewer writes most often. B001 is a bare except clause, which also catches unexpected events such as memory errors, interrupts and system exit, with the advice to prefer except Exception, or to be explicit and write except BaseException, and a note to disable E722 to avoid duplicate warnings. B002 is the unary prefix increment, since Python does not support it and ++n is equivalent to +(+(n)), which equals n, so you meant n += 1. B003 is assigning to os.environ, which does not clear the environment, so subprocesses see outdated variables that disagree with the current process, with os.environ.clear() or the env argument to Popen as the fix. B004 is using hasattr(x, '__call__') to test callability, which is unreliable when x has a custom __getattr__, so callable(x) is the consistent answer. B005 is a .strip() call with a multi-character string, which reads like stripping a substring but is treated as a set of characters. B006 is the mutable default argument, created at function definition time and shared by every call.
The rules that will start an argument in your pull request
Two codes are worth singling out because they are the ones that get pushback. B017 says that assertRaises(Exception) and pytest.raises(Exception) should be considered evil, because they can lead to your test passing even if the code being tested is never executed due to a typo, and the alternatives are to assert for a more specific exception, builtin or custom, or to use assertRaisesRegex if you are using assertRaises, or the match keyword argument if you are using pytest.raises, or the context manager form with a target. The rule is deliberately uncomfortable, because a test that asserts a broad exception type is a test that can pass for the wrong reason. B018 is a found useless expression, either assign it to a variable or remove it, and it is carefully scoped. It considers function calls without side effects such as isinstance, and unary and arithmetic operations such as -x or a * b, but shifts, bitwise operators and the @ operator are left alone, since libraries overload them for their side effects, and so is an operation directly in a try or with block, where it may be run only to see it raise. The trailing note about dangling commas is a nice touch, since print('..'), is the same as an unassigned tuple.
The rest of the catalogue, and the pattern of it
The codes from B007 onwards follow the same pattern of finding a construct that looks like it does one thing and does another. B007 is a loop control variable that is not used within the loop body, with the escape of starting the name with an underscore. B008 is a function call in an argument default, performed once at definition time and reused by every call, with the fix of assigning the call to a module-level variable. B009 and B010 are getattr and setattr with a literal attribute name, where normal property access is clearer and, in the getattr case, a missing default means an AttributeError is raised for a non-existent property while a known name gains no extra safety. B011 is assert False, since python -O removes those calls, so callers should raise AssertionError instead. B012 is break, continue or return inside a finally block, which will silence exceptions or override return values from the try or except blocks. B013 is a length-one tuple literal in an except clause, and B014 is redundant exception types in a tuple such as except (Exception, TypeError), where except Exception catches exactly the same exceptions.
How the plugin is developed and tested
The development dependencies are more interesting than a linter's usually are, and they tell you how the checks are validated. The dev extra lists tox, coverage, hypothesis, hypothesmith at 0.2 or newer, pre-commit and pytest, and the two property-based libraries are the point. A rule that inspects Python syntax has to be tested against syntax nobody wrote by hand, and hypothesis generates programs, while hypothesmith generates them with structure that the tool under test believes is plausible. The rest of the toolchain is conventional, with a tox.ini for the environments, a .coveragerc, a .flake8 configuration for the project to lint itself, a .pre-commit-config.yaml, and a scripts/ directory. The packaging metadata lists the development status as Production/Stable, requires Python 3.10 or newer, and classifies Python 3.10 through 3.15, and the project ships with a DEVELOPMENT.md that the README links for anyone who wants to send a pull request. The author of the package is listed as Łukasz Langa.
Where the opinion belongs, and where it does not
The honest limit of this plugin is that some of its rules are debatable, and the project knows it, which is why the rules are in a plugin rather than in flake8. B014, the redundant exception types, is a rule about clarity that some codebases have good reasons to violate, for instance when the tuple is generated. B012, the finally block rule, is close to absolute. And B017 asks you to change how you write tests, which is a bigger ask than a linter usually makes. The practical approach is the one the project implies, since every code is documented with the reason and the alternative, so a team can read the rule, decide it does not apply to them, and add a targeted noqa rather than disabling the whole plugin. The other limit is the scope, because the plugin covers likely bugs and design problems and nothing about formatting or imports, so a project that wants style, typing and bug detection in one run still needs pycodestyle, pyflakes and a type checker alongside it. The last thing to check before enabling it broadly is the version, since the release numbering is a date, 26.9.30 for the release on 2026-09-30, and the last push was on 2026-10-15, so the project moves often and a pinned version in your requirements file is worth having.
Editorial conclusion
flake8-bugbear is worth adding to any Python project that runs flake8, because the rules it carries are the ones a reviewer would otherwise have to remember, and because each one is documented with the reason and the alternative, so a team can argue with a rule instead of silently disabling it. It is the wrong tool if you want a linter that tells you about style, since everything here is about likely bugs, and it is the wrong tool if you cannot accept opinionated defaults, since the plugin's own premise is that some checks are too controversial to impose on everybody. Before you enable it on an existing codebase, run it in report-only mode and read the counts, because a project that ignores a whole class of these warnings is better served by a per-file ignore list than by turning the plugin off, and remember that B001 duplicates E722 and the documentation says to disable one of them.
Frequently asked questions
How do I install flake8-bugbear?
Run pip install flake8-bugbear. It is then run automatically as part of flake8, and you can check that it was picked up with flake8 --version, which lists the loaded plugins and their versions.
Why are these checks not part of flake8 itself?
Because they are very opinionated and do not have a PEP or standard behind them. Since flake8 is designed to be extensible, the original creators of these lints believed a plugin was the best route, which has resulted in better development velocity for contributors and adaptive deployment for flake8 users.
What does B017 complain about?
It says that assertRaises(Exception) and pytest.raises(Exception) should be considered evil, because they can let a test pass even if the code being tested is never executed due to a typo. The alternatives are to assert for a more specific exception, use assertRaisesRegex, pass the match keyword to pytest.raises, or use the context manager form with a target.
Does flake8-bugbear conflict with other flake8 checks?
Some rules overlap. B001, which flags a bare except clause, says explicitly to disable E722 to avoid duplicate warnings. The plugin also requires flake8 7.2.0 or newer and attrs 22.2.0 or newer, and supports Python 3.10 and above.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/pycqa-flake8-bugbear)Star history chart for your README
[](https://hysenlabs.com/projects/pycqa-flake8-bugbear)Was this useful?