Maintaining an App an AI Wrote for You

·5 min read·Updated
vibe-codingaimaintenancedependenciesoperations

An app built with an AI tool works on day one. The difficulty arrives around month six, when something breaks and nobody holds a mental model of the thing — not you, and not the model, which has no memory of writing it. You're then debugging an unfamiliar codebase under time pressure, which is the worst possible moment to acquire that understanding. The fixes are all cheap and all pre-emptive: pin your dependencies, write down what the app is, keep the specification you built from, and prove you can still deploy it from scratch. The first one matters more here than you'd expect, because every deploy rebuilds your image.

Pin dependencies, because every deploy rebuilds

Generated projects routinely ship loose version ranges, or no versions at all — requests, express, FROM python:3-slim. Locally that's invisible: your environment was resolved once and stays put.

Deployed, it isn't. Your image is built fresh on each deploy, so dependency resolution happens again every time. That means your application can change without you changing anything — and it happens at the worst moment, because the deploy you're doing is usually the one fixing something else.

The failure is memorable: a two-line fix that worked locally, pushed at 11pm, and the build now fails or the app starts behaving differently because a transitive dependency moved.

What to do, all one-time:

  • Commit a lockfile. package-lock.json, poetry.lock, requirements.txt with == pins. If your generated project has none, produce one now while everything works.
  • Pin the base image to a specific minor version — python:3.12-slim, not python:3-slim, and never latest.
  • Pin third-party actions and tools in CI for the same reason.

Then upgrades become something you do deliberately, on a day when you have time, rather than something that happens to you.

Write down what it is

Ten lines in a README, in the repository, answering the questions you won't remember:

What: Discord bot that posts RSS updates to #news
Runs:  python bot.py   (worker, no ports)
Needs: DISCORD_TOKEN, FEED_URL, INTERVAL_SECONDS
Data:  SQLite at /data/seen.db  (volume mounted at /data)
Deploy: git push <remote> HEAD:deploy
Rebuild from scratch: clone, push, set the three env vars above

That's the entire artefact and it takes five minutes. It's also exactly what you'd paste to an AI assistant to get useful help later — the context it can't infer from the code is which variables exist, where data lives, and how it ships.

Add a .env.example with the variable names and no values while you're there. It documents the configuration and stays safe to commit.

Keep the specification, not just the code

If you built the app by prompting, the prompts were the design. Keep them.

At minimum, keep the constraints you built against — the deployment requirements block is worth storing as DEPLOYMENT.md in the repository, because a future session needs the same facts you gave the first one. Without it, your next AI conversation starts by making the same wrong assumptions: localhost binding, files written beside the code, secrets in source.

Keeping it in the repo rather than your chat history means it's loaded rather than remembered, and it survives switching tools.

Prove you can rebuild it

This is the one people skip, and it's the one that decides whether an incident is twenty minutes or a weekend.

Once, while everything works, check that a fresh clone deploys. Not "the app is running" — that only proves it ran once. Clone into a new directory, push to a build branch so nothing live is touched, and confirm the image builds.

What this catches, every time:

  • A file that only exists on your laptop and was never committed
  • A dependency you installed globally and forgot
  • A build step that works because of something cached
  • A variable set in your shell profile years ago

Better to learn that on a Tuesday afternoon than during an outage.

Know where the data is and how to get it out

The code is reproducible; the data isn't. So the question that matters is: if this app vanished, what would you actually lose?

  • Which volume holds it, and at what path.
  • How to take a copy — a database dump, or a file you can download from the Files tab.
  • Whether anything holds data you can't regenerate — user content, accumulated history, uploads.

Take a backup once by hand so you know the procedure works. Nothing is backed up for you, and "I'll write the backup script later" is a plan that fails silently.

The occasional check

A short, boring routine, worth doing a couple of times a year:

  1. Does a fresh clone still build?
  2. Do the dependencies have known problems? Your ecosystem's audit command answers this in seconds.
  3. Is the base image still receiving updates? Pinned versions eventually stop being maintained — pinning is not the same as never upgrading.
  4. Can you restore from your backup? An untested backup is a hypothesis.

Twenty minutes, and it converts "I hope this still works" into knowing.

FAQ

Why did my app break when I redeployed without changing anything?

Unpinned dependencies. The image is rebuilt on every deploy, so versions are re-resolved and can differ from the ones you tested.

What should I pin?

Your dependency lockfile, the base image to a specific minor version, and CI tooling. Never latest.

What's the minimum documentation worth writing?

What the app is, what starts it, which environment variables it needs, where its data lives, and how to deploy. Ten lines in the README.

Should I keep my prompts?

Keep the constraints and specification in the repository. A future AI session needs the same context you gave the first one.

How do I know I can still deploy it?

Clone fresh and push to the build branch. That builds the image without touching your running app.

What if I don't understand the code?

That's normal, and the habits above are what compensate for it. Documented configuration, pinned versions and a tested rebuild let you make changes safely without holding the whole thing in your head.

Wrapping up

Code an AI wrote isn't more fragile than code you wrote — it's less known, and that difference only shows up under pressure. Everything here is about moving that understanding out of your head, or out of a chat session that's already gone, and into the repository where it'll still be when you need it. Pin the versions, write the ten lines, keep the constraints, and prove once that you can rebuild it. Then a broken dependency in March is an afternoon, not a crisis.