Not Every Lint Warning Is Cosmetic A developer working on Python backend cleanup tasks with pylint, flake8, and mypy discovered that seemingly cosmetic lint warnings often reveal hidden contracts in the code. Four cases—enum naming, function signatures, module size, and documentation—showed that fixing warnings led to explicit persistence semantics, ownership checks, clearer module boundaries, and proper documentation. The developer argues that lint warnings serve as early signals for boundary and invariant checks, not just style improvements. How old tools improve the work of new non humans. I noticed this pattern while working through a series of backend cleanup tasks in pylint , flake8 , and mypy : some warnings that I wanted to dismiss as housekeeping kept turning out to be the shortest path to hidden contracts in the code. As a rule, they looked like small cleanup tasks: naming style, function signatures, module size. But once I started fixing them, it became clear that the problem was not cosmetic. The check was simply the first thing pointing at a place where an important contract in the code was still resting on an unspoken assumption. For Python backend development, this is especially noticeable in places where the code already looks plausible and locally reasonable — including cases where the draft was assembled quickly with the help of an LLM. In those places, a warning is sometimes useful not as a demand to “make it cleaner”, but as an early signal: this is where it is worth checking boundaries, invariants, or the shape of the contract. Below are four short cases where cleanup turned out to be not quite cleanup. At first this looked almost cosmetic: I was simply normalizing naming style. class Operator str, Enum : eq = "=" lt = "<" then: class Operator str, Enum : EQ = "=" LT = "<" But that was not the end of it. It turned out that the real contract lives not only in the Python enum, but also in which values SQLAlchemy reads from and writes to the database. So the real fix ended up looking like this: operator: MappedColumn Operator = mapped column SQLEnum Operator, name="operator enum", values callable= enum lower names So the warning was not really about enum style. It was about persistence semantics. From the outside it looked like cleanup, but in practice it forced me to make the contract between the Python enum and stored values explicit. The next signal looked even more mundane: an extra parameter and a messy signature. python async def delete condition db: AsyncSession, item id: UUID, condition id: UUID - None: item = await db.get ChecklistItem, item id When I dug into it, it turned out the warning was not really about the shape of the function. It pointed at an under-specified domain contract. After the fix, the function explicitly required the item id and checklist id pair: python async def delete condition db: AsyncSession, checklist id: UUID, item id: UUID, condition id: UUID, - None: = await get draft item db=db, item id=item id, checklist id=checklist id What mattered here was not the signal about the signature itself, but the fact that it led to an ownership check. After the fix, what became explicit was not the “neatness” of the function, but the dependency between item id and checklist id . The third case was about structure. A warning about module size and shape would have been easy to dismiss as a linting nitpick. But the monolithic schemas.py had in fact stopped being maintainable. app/schemas.py class HTTPErrorDetail BaseModel : ... class AuditEvent BaseModel : ... class ChecklistItemCreate BaseModel : ... class OrganisationCreate BaseModel : ... class TemplateListItem BaseModel : ... ...and so on for hundreds of lines. Instead of adding a suppress, this ended up as a proper package: python app/schemas/ init .py from .audit import AuditEvent from .checklists import Checklist, ChecklistItem, ItemCondition from .enums import Operator, PropName from .orgs import Organisation, Invite, Grant from .templates import TemplateListItem, TemplatePublic So the problem was not style as such. It was that the codebase was already asking for clearer boundaries. In this case, the file-size warning was not noise, but an early sign that the module needed to be split along responsibilities. Another case initially looked purely documentation-related. pylint and flake8 required proper docstrings for functions, methods, and classes, and from the outside this is easy to read as hygiene for the sake of hygiene. But in several places the fix worked differently. For example, in checklist router.py the docstring stopped being a generic phrase about a “router for Checklist entity” and turned into a short description of the real lifecycle contract: that published versions are immutable, that draft creation is explicit, that editor mutations operate only on the draft layer, and which denial/error semantics are considered normal here. A similar thing happened with middleware. There the docstring started fixing not a paraphrase of the method, but important boundary conditions: when Session-Log-ID is required, why the middleware returns 400 , where exactly the request-scoped DB session lives, and why this is ASGI middleware rather than BaseHTTPMiddleware . The framing from Arun Rajkumar’s post about agent-driven workflow https://dev.to/mickyarun/i-replaced-scrum-jira-and-our-wiki-with-12-ai-agents-on-a-mac-mini-o7o is also useful here, because in that framing code and related artifacts act as a source of working context. Read that way, a meaningful docstring is not decorative documentation, but another explicit form of a local contract, useful not only to a human, but also to an AI agent: it no longer has to reconstruct those constraints from the implementation every single time. Of course, not every docstring has that effect. If a checker only pushes on form — imperative mood, blank lines, section headers — that is more a matter of formatting discipline. That is still useful at least as a factor of consistency, but it is a different kind of value. But where a docstring fixes preconditions, boundaries, error semantics, or lifecycle assumptions, a documentation warning stops being pure cosmetics. Across all four cases, what matters is not the warning itself as a ritual, but the move from implicit to explicit. In one place I had to make persistence semantics explicit, in another an ownership boundary, in the third module boundaries, and in the fourth a local API or middleware contract. That is probably the most useful way to read such signals. Not as an order to make the code neater, but as a reason to check whether an important contract is still resting on a silent “this is obvious anyway.” This does not mean every warning hides a deep problem. But if it touches storage format, ownership, input shape, or a module boundary, I would no longer treat it as pure cosmetics.