What Lint Warnings Really Mean for Your Lovable Project (and How to Stop Them Owning You)

Ever notice that one loose cable snaking out from under your desk? No big deal. But then it’s joined by a second, and a third. Six months on, you’re staring at a tangled jungle of wires you’re afraid to touch. That, in a nutshell, is what happens with linter warnings in your codebase.
TL;DR: Linter warnings are small red flags that look harmless alone but multiply into a noisy mess that hides real, dangerous bugs. A project with 20 warnings today can easily have 300 in six months, making it impossible to spot the critical ones. The solution isn’t a frantic "fix-all" command; it’s a methodical, four-pass triage that separates the harmless tidying from the high-stakes fixes.
What are linter warnings?
Think of a linter as an automated proofreader for your code. It’s like spellcheck, but for programming. A linter doesn’t actually run your application; instead, it scans your source code and says, “This might work, but something here looks risky, messy, inconsistent, or just plain wrong according to our team’s rules.”
It’s not always flagging something that’s technically broken. More often, it’s pointing out a future problem—code that will be harder to read, maintain, scale, or debug down the line.
For example, imagine this perfectly valid bit of JavaScript:
const name = "Francisco";
console.log("Hello");
Your app will run without a problem. But a linter will raise a flag: 'name' is assigned a value but never used. While the app works, this unused variable is just noise. It adds a tiny bit of cognitive load for the next developer who has to read it. Now, multiply that by a hundred.
So, what’s the big deal?
One linter warning is just a loose cable. Fifty warnings are a fire hazard. When your console is flooded with hundreds of yellow alerts, you stop seeing any of them. Your team develops “warning blindness,” and that’s when the truly nasty bugs sneak past.
Linter warnings are your first line of defence, helping you catch:
- Bugs before they happen: Like using a variable before it’s been assigned a value.
- Unused code: Dead functions, dangling imports, and zombie variables that bloat your bundle size.
- Inconsistent formatting: The "shared kitchen" problem, where nobody can find anything because it’s all organised differently.
- Risky patterns: Code that could lead to security vulnerabilities like Cross-Site Scripting (XSS).
- React hook mistakes: Missing dependencies in a
useEffectare a classic source of baffling bugs. - Accessibility gaps: Like an image missing its
alttext, making your app unusable for some people. - TypeScript sloppiness: The dreaded
anytype, which completely defeats the purpose of using TypeScript in the first place.
The Silent Accumulation: Why Good Codebases Go Bad
Warnings don’t appear overnight. They creep in, one by one. A good codebase is like a well-tended garden; without regular weeding, it gets overrun. This usually happens for a few common reasons:
1. “If it works, don’t touch it.” This is the number one cause. A developer builds a feature, the page loads, and it seems to work. They see a few warnings but ignore them to move on to the next ticket. Over time, these ignored warnings become part of the furniture.
2. The speed of AI-powered prototyping. Tools like Lovable, Cursor, and Replit are incredible for getting from idea to working prototype at lightning speed. But this speed comes at a cost. AI-generated code often leaves a trail of digital crumbs: unused imports, temporary console.log statements, over-complicated functions, and loose TypeScript types. The product moves forward, but the cleanup is left for "later".
3. Refactoring without cleaning. You rename a component, move some logic into a new hook, or split a giant file into smaller ones. The app still functions, but you’ve left behind a ghost town of old props, forgotten types, unused helper functions, and stale state variables. Each one is a new warning.
4. Inconsistent team standards. One developer prefers function () {}, another prefers () => {}. The linter is configured to enforce one standard, but without discipline, people ignore it. The codebase becomes a mess of conflicting styles, making it harder for everyone to read and contribute.
5. AI over-production. When you ask an AI assistant for a component, it can sometimes be a little too helpful. It might add extra state variables for edge cases you don’t have yet, pre-emptively import libraries you won’t need, or create generic helper functions that are never actually used. AI is great at planting the seeds, but a human still needs to do the weeding.
The Three Risk Tiers of Warnings
Not all warnings are created equal. To see why, let’s imagine a fictional recipe app—we’ll call it ForkLog—and look at three real warnings that might appear in its dashboard.
| Tier | Example in ForkLog | Real-world impact |
|---|---|---|
| Low | Unused import of Lemon icon in RecipeCard.tsx | Bigger bundle, slower loads. Safe to auto-fix. |
| Medium | cookTime: any in the recipe form | User types "forty" into a minutes field → cards display NaN min. Needs manual typing. |
| High | Unvalidated imageUrl rendered in an <img> tag | Attacker submits javascript: URL → XSS. Audit only, never auto-fix. |
How Warnings Compound Over Six Months
Let's follow ForkLog’s journey from a clean launch to a noisy, bug-prone application.
- Month 1: ForkLog launches with 18 warnings. They’re mostly unused imports. Nobody notices or cares.
- Month 2: A new “Meal Plans” feature adds 40 more warnings, mostly from a hastily-typed API response full of
anytypes. - Month 4: A contractor copies the same pattern into the “Shopping Lists” feature. The warning count jumps to 160. The first bug ships: a recipe card displays “NaN minutes”.
- Month 6: The project now has 312 warnings. A new developer joins the team and quickly learns to ignore the constant stream of yellow text. An unescaped review body, allowing a severe HTML injection vulnerability, sits unnoticed in that list for three weeks.
That is the real cost. Warnings don’t just break your app—they break your team’s attention. Once the list is too long to scan, every new warning becomes invisible, including the truly dangerous ones.
The 4-Pass Triage Framework
You can’t just run one command and fix 300 warnings without breaking something. The answer is a methodical triage, breaking the problem down into distinct, safe passes. Commit after each one.
- Pass 1 — Auto-fixable Lint: Run your project's built-in fixer (e.g.,
eslint --fix). This will handle all the low-hanging fruit: unused imports, formatting issues, and simple rule violations. Commit these changes on their own. - Pass 2 — Dead Code Elimination: Use a dedicated tool like
kniports-pruneto find unused files, orphaned exports, and dead components. Delete them. This is often a huge win. Commit this on its own. - Pass 3 — Intentional Disables: Sometimes, a warning flags a pattern that is genuinely acceptable for a specific reason. In these rare cases, add a targeted disable comment (e.g.,
// eslint-disable-next-line a-specific-rule) with a one-line explanation of why it’s necessary. Never disable a rule for an entire file. - Pass 4 — The Real Typing Work: This is the slow, manual, and most important pass. Go file by file and replace every lazy
anywith a proper, specific type. Build your application after each file to ensure you haven't broken anything. This is where most medium-risk bugs are caught and fixed.
Cautions: Guardrails for Your Cleanup
Before you delete a single line of code, memorise these rules. They prevent a well-intentioned cleanup from becoming a production-down incident.
- Never let an AI assistant blindly replace every
anywithunknown. It feels like a fix, but it just kicks the can down the road, often causing dozens of new TypeScript errors to cascade through your app. - Never blanket-disable a rule across an entire folder or project. You will inevitably hide the next real bug that the rule was designed to catch.
- Never touch security-critical files in an automated sweep. This includes edge functions, authentication flows, billing webhooks, or anything that handles money or personal data (PII). Audit these files manually.
- Always commit your changes between passes. If Pass 3 breaks something, you want a clean, simple rollback, not an impossible-to-unpick mega-commit.
- Always run your app and its tests after each pass. Don’t wait until the end to find out what you broke.
A Cheat Sheet for Triage
Here’s how to delegate the work between automated tools, AI assistants, and human developers.
| Tier | Auto-fix? | AI assistant role | Human review |
|---|---|---|---|
| Low | Yes | Run the fixer | Skim the diff |
| Medium | One file at a time | Propose types, you approve | Read every change |
| High | Never | Report only — no edits | Fix by hand, test, peer review |
Copy-paste this Lovable skill
This entire triage framework is captured in the Lovable skill below. To use it, save the following code as .workspace/skills/lint-triage/SKILL.md in any Lovable project. Then, just open the composer and type something like /lint-triage a few files to get started.
---
name: lint-triage
description: Safely reduce ESLint + TypeScript warnings in a Lovable project using a 4-pass tiered framework. Triggers on phrases like "lint cleanup", "fix warnings", "reduce lint noise", "triage lint".
---
# Lint Triage Skill
You help the user safely reduce lint warnings without breaking the app.
## Operating rules
1. NEVER run a single mega-sweep. Work in passes; commit between each.
2. NEVER replace `any` with `unknown` across multiple files at once.
3. NEVER blanket-disable a rule for a folder or file.
4. NEVER auto-edit code in: edge functions, auth, billing/webhooks, anything touching PII or money. For those files, report findings only.
5. ALWAYS run the build after each pass. Stop on first failure.
## Workflow
### Step 0 — Inventory (read-only)
Run the lint command, group warnings by:
- rule id
- directory (`src/components`, `src/hooks`, `supabase/functions`, etc.)
- risk tier (Low / Medium / High — use the rubric below)
Present the table to the user. Do not edit anything yet.
### Risk rubric
- LOW: unused imports/vars, formatting, missing keys, simple a11y.
- MEDIUM: `any` types, missing hook deps, non-exhaustive switches, unsafe optional chaining.
- HIGH: anything in edge functions, auth, billing, webhooks, file uploads, raw HTML rendering, URL handling, SQL building.
### Step 1 — Low tier (auto)
Run the project's auto-fixer (`eslint --fix` or equivalent). Show the diff summary. Ask the user to approve the commit.
### Step 2 — Dead code (auto with review)
Identify unused exports/files. Present the list. The user picks which to delete. Commit on its own.
### Step 3 — Medium tier (one file at a time)
Pick ONE file. Propose concrete types (not `unknown`). Show before/after. Wait for approval. Apply. Build. Commit. Repeat.
### Step 4 — High tier (REPORT ONLY)
Produce a written audit: file, line, rule, recommended fix, why it matters. Do NOT edit. The user assigns this to a human reviewer.
## Output format
After each pass, report:
- warnings before / after
- files changed
- build status
- what's left for the next pass
## Refusals
If the user asks to "fix all warnings at once", refuse and explain the 4-pass framework. If they insist, do Step 0 only and stop.How the Skill Works: A Prompt Breakdown
This skill isn't magic; it’s a carefully constructed set of instructions that guide the AI assistant to behave safely.
- Frontmatter: The
name,description, and trigger phrases ("lint cleanup," "fix warnings") teach Lovable when this skill is the right tool for the job. - Operating Rules: These are the non-negotiable guardrails. By stating them upfront, we prevent the assistant from taking dangerous shortcuts like a mega-sweep or auto-editing sensitive code.
- Step 0 — Inventory: The skill forces a read-only audit first. This ensures you and the AI both understand the scope of the problem before a single file is changed.
- Risk Rubric: This gives the assistant an explicit model for classifying warnings as Low, Medium, or High risk. This prevents it from quietly promoting a high-risk fix into an automated edit.
- Steps 1–3 — Graduated Automation: The workflow moves from fully automated (for low-risk fixes) to semi-automated (for dead code) to one-file-at-a-time (for tricky typing work), with human approval at each stage.
- Step 4 — Report Only: This is the hard firewall. For anything in a high-risk area like authentication or billing, the AI’s job is simply to report its findings, not to act on them.
- Refusal Clause: If you try to override the safety rules by asking to "fix all warnings at once," the assistant is instructed to refuse and explain the safer, multi-pass framework instead.
Final Word
Warnings are not your enemy—silence is. A project with a dozen visible warnings that the whole team reads and understands is infinitely healthier than a project with "zero warnings" because the linter has been disabled. Keep the list short enough to scan, and the dangerous bugs will have nowhere left to hide.
Get the next essay in your inbox
Practical notes on building with AI, shipping fast, and growing a one-person studio.