10 Red Flags I Find in 90% of Seed-Stage Codebases
The specific patterns I see again and again when I open a seed-stage codebase for the first time — what they mean, why they happen, and how to fix them before they cost you a fundraise.
I have opened a lot of seed-stage codebases in the last few years — as a fractional CTO walking into an engagement, as an independent technical diligence reviewer for VCs, and occasionally as a favor to a founder friend the night before a pitch. The surprising thing is not how different they are. It is how similar.
There is a short list of patterns I see in almost every seed-stage codebase. None of them are fatal on their own. All of them are fixable. But when three or four of them show up in the same repository, the story they tell is the same: a team that shipped fast, never came back to clean up, and is now carrying invisible drag on every future feature. In a technical diligence that drag becomes a line item investors read carefully.
This article is the list. Ten red flags, in the order I usually spot them, with the fix for each.
1. The environment file is the documentation
You clone the repo, you run npm install, you try to start it, and nothing works. You look for a README. The README is the default Next.js one. You look in the docs folder. There is no docs folder. Eventually someone on Slack DMs you a .env.example with 40 variables, half of which are undocumented, and you start the painful process of guessing which ones you actually need.
What it means: Onboarding a new engineer takes days instead of hours. Every person who has ever joined the team has independently solved the same problems. The founder has become the tribal knowledge holder and cannot get out of that role.
The fix: Write a real README. Ten minutes of honest writing is enough. Then add a CLAUDE.md or equivalent project instructions file so AI assistants actually help instead of hallucinating. Both should answer: what is this, how do I run it, what are the conventions, what breaks in weird ways.
2. No tests, or tests that nobody runs
The codebase has a __tests__ folder with six files in it. Four of the files are the default scaffolding from a template. The other two test trivial utility functions. There is no CI step that runs them. The team's confidence in changes comes from running the app locally and clicking around.
What it means: Every deploy is a coin flip. Refactoring is terrifying, so it does not happen, so the code ossifies. As the team grows, velocity drops because nobody is sure what they are breaking.
The fix: You do not need 80% coverage at seed stage. You need a small number of tests on the core business logic and on anything that has broken in production before, and you need them to run in CI on every PR. Ten meaningful tests are worth more than a thousand auto-generated ones.
3. The single load-bearing engineer
One person wrote 70% of the code. They also wrote the other 30% but forgot. They are the only one who can explain why the billing module does what it does, and the billing module is the thing that makes the company money. If they take a week off, the company quietly stops shipping.
What it means: From a diligence perspective, this is a deal-risk item. Investors are underwriting a key person, not a company. From a founder perspective, this is a fragility that turns into a crisis at the worst possible moment — usually during a fundraise or a hire transition.
The fix: Start pairing another engineer into the load-bearing modules now. Document the parts that only live in one head. Over 60 days, transfer enough knowledge that at least one other person can safely change the critical path.
4. Secrets committed to the repo
Git history contains .env files. Or API keys hardcoded in a config file. Or a creds.json that someone accidentally committed three months ago and then "removed" without rotating the credentials.
What it means: You have a security incident whether or not you know it. Any engineer who ever had a clone of the repo still has those credentials. Any service that scans GitHub — and several do — already knows about them.
The fix: Rotate every credential that has ever been in the repo. All of them. Today. Then install a pre-commit hook (gitleaks, trufflehog, or similar) that blocks future commits with secrets. Then move secrets to your hosting platform's secret manager and stop writing them down.
5. The "just one file" file
There is one file that is 3,000 lines long. It has a name like helpers.ts or utils.ts or app.tsx. It imports from everywhere and is imported by everywhere. Nobody wants to touch it. New code keeps getting added to it because that is where the existing code is.
What it means: You have a dependency black hole. Any change in that file has unpredictable ripple effects. The reason nobody refactors it is the same reason nobody can work in it safely — there are no boundaries, no tests, and the mental model required to change one function requires understanding all of them.
The fix: Do not try to rewrite the whole file in a weekend. Pick one function, move it to a properly scoped module with a test, and update the callers. Repeat. Over a few weeks the file shrinks to something manageable and the team learns a pattern for dealing with the next one.
6. Duplicated logic that has drifted
The same thing is implemented three times in three different places. Usually this happens because a feature was copy-pasted and then each copy was modified slightly. Now the three versions have drifted. A bug fix in one is not a bug fix in the others. Nobody is sure which version is canonical.
What it means: Your bug reports will be confusing and your fixes will be incomplete. You will fix the same bug in production three times and be baffled why it keeps coming back.
The fix: When you find duplication, pick the best version, make it canonical, and delete the others. Resist the urge to immediately re-abstract it into a fancy shared utility — just use the same function everywhere first, then refactor if a pattern emerges. Premature abstraction is how duplication gets started in the first place.
7. Error handling that swallows everything
Every try/catch block is catch (e) { console.error(e); } or worse, catch (e) {}. Errors are not reported to a central place. Silent failures pile up. The app appears to work but has quietly been losing data or skipping operations for weeks.
What it means: Your incident response is purely reactive — you find out about problems when customers tell you. You cannot trust any of your metrics, because half of the failures never show up anywhere.
The fix: Install Sentry (or equivalent) in 30 minutes. Then audit every silent catch block and decide, for each one, whether the error is truly expected (and should be logged with context) or unexpected (and should fail loudly or report to Sentry). No more empty catches.
8. A frontend with no state management discipline
Every component manages its own state. Global state is sprinkled into three different places — some useContext, some a Zustand store, some random module-level variables. The same piece of data (the current user, the current org) is fetched four times on every page load.
What it means: The app is slower than it should be, bugs appear when different sources of truth disagree, and new features take longer because engineers have to figure out where each piece of state lives.
The fix: Pick one state management approach per concern. Server state goes in React Query or equivalent. Client state goes in one Zustand/Redux store or in contexts. Write it down in the README so the next engineer does not reinvent it.
9. The dead code graveyard
Entire folders of code that are not imported from anywhere. Feature flags that default to off and have not been touched in 18 months. Routes for features that were killed. Dependencies in package.json that nothing uses. A legacy/ folder that is somehow still in production.
What it means: Dead code is not free. It gets scanned by security tools and produces false positives. It confuses new engineers. It bloats build times. And every so often, someone accidentally wires a piece of dead code back into production and causes an incident.
The fix: Delete it. All of it. If you are worried, delete it in a branch and keep the branch for 30 days. You will not need it. The scariest part of deleting dead code is realizing how much of it is dead.
10. No observability beyond "the app seems up"
You have no metrics dashboard. You have no error tracking (or it was set up and nobody looks at it). You have no logs beyond what was printed to the console in development. Your incident detection is a customer writing "hey is your site down?" in Intercom.
What it means: You are flying blind. You have no idea what normal looks like, so you cannot tell when something is abnormal. Performance degrades slowly and nobody notices until customers leave.
The fix: Install the observability trinity — errors (Sentry), logs (Axiom, Baselime, or similar), and basic metrics (your hosting provider's built-in dashboard or something light like Grafana Cloud). Add one SLO you actually care about (e.g. "95% of API requests under 500ms"). Review it weekly.
The meta-pattern
If you read the ten red flags carefully, you will notice something. None of them are about exotic technology choices. None are about architecture. They are almost all about the basic disciplines of running a codebase as an asset rather than a liability — documentation, testing, error handling, observability, and the willingness to delete things.
Seed-stage startups neglect these disciplines because they feel optional. They are not. They are the difference between a codebase an investor can read with confidence and one that raises a dozen concerns per file. They are also the difference between a team that ships at 100% of its speed and a team that spends a third of its time fighting the codebase it wrote six months ago.
Counterpoint: perfection is the enemy
A warning. You can spend all of Q2 fixing these ten things and ship nothing new. That is also a failure mode. The goal is not a perfect codebase. The goal is a codebase with no more than two or three of these red flags active at any time, and a cultural commitment to not letting the others sneak back in. Pick the top three from this list, fix them in two weeks, and move on.
Your next step
Open your codebase today and go through this list honestly. For each of the ten items, give yourself a green (fine), yellow (present but minor), or red (serious). Any reds get scheduled into next week. Any yellows get a ticket.
If you cannot tell honestly — because you wrote most of the code and have lost perspective — get an outside read.
Where I come in
A 2-day code review against this exact list is one of my most common one-off engagements. I open the repo, run through the ten red flags, write a 3–5 page report with specific line references and severity ratings, and debrief the founder and the lead engineer. It is the fastest way to know what is actually wrong before you spend money fixing the wrong thing. Book a call if you want a fresh set of eyes on your codebase.
Related reading: Inside a Technical Due Diligence · The Seed-Stage Stack · CLAUDE.md Is the New README
Want an honest read on your codebase before investors see it? Book a call.
Get in touch →