When code is written to match a "vibe" rather than a specification, the result often runs—until it doesn't. This page catalogs the failure modes that emerge when intuition substitutes for design, with concrete examples you can recognize and avoid. It matters whenever an AI-assisted workflow, a hackathon prototype, or a rapid-iteration cycle bypasses review, testing, or documentation steps. The anti-patterns below are not language-specific; they apply wherever code is produced faster than it is understood.

Prototyping Without a Safety Net

A reader tries to spin up a working prototype in hours rather than days. The intent is speed, but the anti-pattern is leaving the prototype as production code.

Exact command

curl -s -X POST http://localhost:8080/run \
  -H "Content-Type: application/json" \
  -d '{"prompt":"build a user service with auth and api"}'

What silently fails

The server starts and responds 200 OK, but the generated service binds to 0.0.0.0, exposes every endpoint, and writes session tokens to plaintext files in /tmp. No rate limiting, no input validation, no audit logging. The prototype functions as a demo, but each of those omissions becomes a vector the first time it reaches a public network.

Exact error text (produced in production after deployment)

2025/03/12 08:14:23 http: server reaching connection limit due to unbounded connection pool
2025/03/12 08:14:25 security: token leaked in /tmp/session_192.168.1.42.json

What you only learn after it breaks

When the prototype is promoted to staging, the absence of a Dockerfile USER directive means the container runs as root. A single path traversal in the generated API yields root-level file read. The fix requires rebuilding the image with a non-root user, adding RUN useradd -m app and USER app, and regenerating secrets. If the original prompt included “use environment variables for secrets,” that instruction was likely ignored by the generator.

Debugging AI-Generated Code You Did Not Write

A reader encounters a stack trace in a service they did not author. The intent is diagnosis, but the anti-pattern is reading intent into code that was synthesized from a prompt.

Exact command

git log --oneline --all | head -20

Sharp edge: commit messages that say “fix” or “update”

Every commit message in the repository is feat: add login or fix: runtime error. No diff is attached, no ticket reference, no description of what changed. When git show <sha> is run, the patch adds a try/catch that swallows the original exception:

try:
    result = db.query(sql)
except Exception as e:
    logger.error("query failed")  # original traceback lost
    raise

The reader sees query failed in the log and has no way to determine whether the failure was a timeout, a deadlock, a schema mismatch, or a syntax error. The only recovery path is to enable log_error_verbosity = verbose in the database configuration and restart the service.

Exact configuration key ( PostgreSQL )

# postgresql.conf
log_min_duration_statement = 200
log_error_verbosity = verbose

Setting these two keys before restarting systemctl restart postgresql causes every slow query to emit its full text, parameters, and the query plan node that exceeded the threshold. Without them, the only signal is statement_timeout exceeded.

Modifying Vibe-Coded Components

A reader needs to add a field to a data model generated by a vibe-coding workflow. The intent is incremental change, but the anti-pattern is treating generated code as hand-written.

Exact command

psql -c "\d users"

Result

                                 Table "public.users"
 Column   |  Type   | Collation | Nullable | Default
----------+---------+-----------+----------+---------
 id       | integer |           | not null | nextval('users_id_seq'::regclass)
 email    | text    |           |          |
 created_at| timestamp with time zone |           | not null | now()

The table was created by a prompt that specified “users table with email and timestamps.” Adding phone_number requires either editing the migration file manually or re-running the generator. If the generator is rerun, any hand-written changes are overwritten. If the migration is edited manually, the next generator run may produce a conflicting schema definition, and the migration tool will refuse to apply both.

Exact error text ( migration tool )

ERROR: duplicate column phone_number exists
HINT: Add IF NOT EXISTS or use ALTER TABLE ... DROP COLUMN first.

The reader must choose between keeping the hand-edited migration (and risking future conflicts) or accepting a new generator output (and losing prior modifications). There is no metadata tag that marks a file as “generator-produced” versus “hand-written,” so the decision is manual and permanent.

Deploying Vibe-Coded Containers

A reader pushes a container to a registry, intending to deploy it to a cluster. The anti-pattern is assuming the image is complete because it starts without error.

Exact command

docker build -t myapp:latest .

What silently fails

The Dockerfile was generated with FROM node:20 and COPY . /app, but the prompt did not include a CMD or ENTRYPOINT. The image builds successfully, and docker run myapp:latest starts a shell with the working directory set to /app. No process runs, no port is exposed, and the container exits immediately after the shell command finishes.

Exact command to reveal the issue

docker run -d myapp:latest
docker ps -a

Result

CONTAINER ID   IMAGE        COMMAND       CREATED          STATUS                      PORTS     NAMES
a1b2c3d4e5f6   myapp:latest  "/bin/sh"     2 seconds ago    Exited (0) 2 seconds ago                       nostalgic_mcnulty

The reader must add a CMD ["node", "index.js"] or equivalent to the Dockerfile and rebuild. If the original prompt specified “use port 3000,” that instruction may have been recorded only in a README or a comment, not in the exposed EXPOSE directive.

Exact Dockerfile fragment to add

EXPOSE 3000
CMD ["node", "index.js"]

After docker build -t myapp:latest . and docker run -p 3000:3000 myapp:latest, the application listens on the expected port. Without the EXPOSE flag, docker inspect will show HostPort as 0 unless -p is used at runtime.

Integrating Vibe-Coded APIs

A reader needs to connect a frontend application to an API generated by a vibe-coding tool. The intent is integration, but the anti-pattern is assuming the API surface is stable.

Exact command

curl -i http://localhost:8080/api/users

Result

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store

[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]

The response shape is a flat array of objects. The reader builds a TypeScript interface:

interface User {
  id: number;
  name: string;
}

Two weeks later, the generator is run again with a slightly different prompt, and the API now returns:

[{"userId":1,"fullName":"Alice","metadata":{"role":"admin"}}]

The userId field replaces id, fullName replaces name, and a nested metadata object appears. The frontend crashes with Cannot read properties of undefined (reading 'name').

Sharp edge: undocumented field renames

The generator does not emit a schema or changelog. The only way to detect the change is to inspect the raw response, compare it against the last known shape, or add runtime validation (e.g., zod or ajv) that tolerates unknown fields. Without validation, any field rename or addition is a breaking change.

Exact validation snippet (JavaScript, zod)

const userSchema = z.object({
  id: z.number(),
  name: z.string(),
});

app.get("/api/users", async (req, res) => {
  const raw = await fetch("http://generator:8080/api/users").then(r => r.json());
  const parsed = userSchema.safeParse(raw);
  if (!parsed.success) {
    return res.status(502).json({ error: "unexpected payload shape" });
  }
  res.json(parsed.data);
});

Adding this guard requires a deploy cycle, but it prevents the silent failure mode where malformed payloads propagate through the system.

Checklist: Before You Prompt

Before running any generator-assisted workflow, run through these concrete checks. Each one is a concrete action, not a principle.

  • Run git log --oneline --all and verify every commit message references a ticket or issue number. If messages are vague, treat the history as opaque and plan a manual audit before promotion.
  • Inspect the generated Dockerfile with docker build -t tmp . and then docker run --rm tmp cat /app/Dockerfile. Confirm that USER, EXPOSE, and CMD are present and intentional. If any are missing, add them before the image is pushed.
  • Check the API response shape with curl -i or an HTTP client. Parse the JSON and compare it against the interface or model you intend to consume. If the shape differs from documentation, add runtime validation before the data enters your business logic.
  • Enable database verbosity with SET log_error_verbosity = verbose; and SET log_min_duration_statement = 0; for a single session, then run the critical query. Capture the output. If the log does not include the query text, parameters, and plan, the debugging window is narrower than expected.
  • Never treat a generated migration as final unless the generator emits a checksum or migration ID that is stored in version control. If no such ID exists, plan to review the migration diff before alembic upgrade head or sequelize db:migrate.

The common thread across all these anti-patterns is the same: code that is produced faster than it is examined will eventually be depended upon, and the cost of that dependency is paid in bugs, outages, and refactoring effort. The anti-patterns listed here are not inevitable; each one is avoidable with a concrete check, a second look, or a minimal validation layer. The reader who internalizes these checks spends less time recovering from failures and more time building features that survive contact with production.

This page was rewritten on 11 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.