When Your Codebase Starts to Congeal

When Your Codebase Starts to Congeal
When Your Codebase Starts to Congeal

You open a file and cannot tell what it is doing.

Not because it is especially complex. The comments are fine. The variables have sensible names. There is a README, and someone clearly spent time writing it.

Something else is wrong.

The patterns do not match the adjacent module. Error handling follows a style nobody else uses. The data shapes change halfway through the call chain. A helper exists in three slightly different forms. The file feels like it was translated from several programming languages and then translated back.

It works, though. The tests pass. It shipped two sprints ago. Nobody has complained.

This is how a codebase congeals.

The harmless glob

Congealing Slop begins with small, defensible pieces. A developer asks Copilot for a quick function. Someone prompts Claude for a utility module. A late-night session produces a data transformer. Each piece solves a real problem. Each can survive its own code review.

The trouble starts when the pieces touch.

One developer asks for an authentication wrapper. Another asks for an API client. A third generates validation logic. None of them gives the tool the context of the other work. Each prompt is an island. Eventually someone builds bridges between the islands, and those bridges become the architecture.

The code does not crash. That would be easier to diagnose.

It almost works.

It passes tests, survives the demo, and reaches production. Underneath are conflicting conventions, duplicate logic, incompatible assumptions, and structural hacks nobody remembers writing because, technically, nobody wrote the whole thing. The model produced one piece. A developer accepted another. A different developer adapted a third.

The system has authors but no shared dialect.

Why the bug appears somewhere else

A bug in congealed code rarely lives where the error appears.

The API client returns an invalid date. The cause is in the validation layer. The validator expects one format because a generated helper assumed it. That helper was copied into another module with a different assumption. The failure surfaces at the client because that is where the incompatible pieces finally meet.

Each local decision seemed reasonable. The system is unreasonable as a whole.

That is what separates this problem from ordinary technical debt. A normal shortcut usually has a visible tradeoff. Someone knows what was sacrificed. Congealed code hides the tradeoff behind locally correct work.

A giant refactor is not an automatic cure. If the team generates new changes without first agreeing on boundaries and conventions, the refactor creates fresh seams. The mess splits into smaller messes, each carrying half the old assumptions and none of the shared history.

The AI will not maintain a team dialect on its own. Consistency is the team’s job.

Stop the spread before the cleanup

Set the conventions before the next prompt. Use one formatter and one linter. Put important rules in the repository instead of leaving them in a senior developer’s memory. Keep architecture decisions short, but record why a boundary or dependency exists.

Give the assistant useful context. Point it to the existing interfaces, the relevant module, and the project rules. Ask it to find an existing helper before generating a new one. A prompt that says "add validation" is an invitation to invent a local solution. A prompt that says "use the validation pattern already established in orders/ and explain where it belongs" has a better chance of producing code that fits.

Integrate early. Short-lived branches and frequent CI expose mismatched assumptions while they are still cheap to fix. Generate a dependency graph occasionally and inspect surprising connections. If every module knows about every other module, the graph is telling you something the green dashboard may not.

Review generated code for fit, not just correctness.

Does it use the team’s patterns? Does it belong in this layer? Is it duplicating a helper that already exists? Does the abstraction solve a current problem or create a future one? Will the next person understand why it was written this way?

Those questions take longer than accepting a plausible diff. They also prevent a codebase from becoming a museum of disconnected good ideas.

"It simply happened" is how slop starts congealing. Make choices visible before the next sprint covers them. Otherwise the repository keeps passing tests while becoming harder to change, one reasonable decision at a time.

>>