Skip to content
codingpipe.com

What I keep fixing in AI-generated code

I keep fixing the same issues in AI-generated code — problems with ownership, boundaries, and duplication. Even with good prompts and design docs, the code still has them. The fixes are usually small, but they add up.

Ownership

The most common ownership problem is simple: the rule lands in whichever file the agent was already touching.

function assignTask(task, userId) {
    if (task.status === "done" || task.status === "cancelled") {
        throw new Error("Cannot assign closed task");
    }
 
    task.assigneeId = userId;
}

What makes a task assignable now lives in the caller. The next use gets a slightly different version. One check forgets Cancelled, another adds Archived, a third never sees the first rule at all.

The fix is to give the rule an owner — the abstraction that has the data and behavior it operates on.

task.assignTo(userId);
 
class Task {
    assignTo(userId) {
        if (this.status === "done" || this.status === "cancelled") {
            throw new Error("Cannot assign closed task");
        }
 
        this.assigneeId = userId;
    }
}

If code inspects an object’s state to decide what that object is allowed to do, that rule belongs inside the object.

Boundaries

One boundary failure is the method that does the whole workflow.

async function handleAssign(input) {
  validate(input);
  checkPermissions(input.userId);
 
  const task = await loadTask(input.taskId);
  task.assigneeId = input.assigneeId;
  task.status = "in_progress";
 
  await saveTask(task);
  await sendNotification(input.assigneeId);
  return toDto(task);
}

Validation, authorization, domain state change, persistence, notification, mapping: all in one method. It looks efficient in a diff. It also turns into the method nobody wants to touch later.

async function handleAssign(input) {
  const task = await loadTask(input.taskId);
  task.assignTo(input.assigneeId);
  await saveTask(task);
  await publishTaskAssigned(task, input.assigneeId);
  return toDto(task);
}

I want that method to read like a table of contents.

Duplication

This one hides well because the names are different.

const overdueForReminder = invoice.dueDate < now && !invoice.paid;
const overdueForReport = invoice.dueDate < now;

Both mean overdue. One checks paid. The other does not. The task got solved twice instead of the concept being defined once.

invoice.isOverdue(now)

Give overdue one definition and every caller gets the same answer.


Conclusion

These are the edits I keep making:

Most of the generated code is not obviously broken. It is just shaped by the nearest context more than by the system around it.

How edit forms should load dropdown data