Most production bugs fall into a handful of recognizable categories: the same mechanical mistakes, repeated across thousands of codebases.
Null references. Unhandled promises. Type mismatches. Missing error boundaries.
These errors plague production systems because writing error-free code at scale is genuinely difficult. You're building systems that handle millions of requests, integrating with dozens of services, managing async operations across distributed networks. Mistakes happen.
This is where automated bug fixing fits: the common, mechanical errors that follow recognizable patterns. Here are the top 7 categories with examples, and the kind of fix an AI agent writes for each one.
1. Null or Undefined Reference Errors (the most common category)
This is the #1 cause of production failures. JavaScript calls .split() on undefined. Python tries to access a dict key that doesn't exist. Go receives a nil pointer. Same category of failure, different syntax.
Example: JavaScript
// Production error: Cannot read property 'email' of undefined
function sendWelcomeEmail(user) {
const email = user.email.toLowerCase(); // Crashes if user is null/undefined
sendEmail(email, welcomeTemplate);
}
What causes it:
- API returns
nullinstead of expected object - Async operation completes but doesn't set expected data
- Third-party service returns different schema than expected
- Default parameter not provided
How AI fixes it:
The fix recognizes that user could be null and adds a guard:
function sendWelcomeEmail(user) {
if (!user || !user.email) {
console.warn('User or email is missing, skipping welcome email');
return;
}
const email = user.email.toLowerCase();
sendEmail(email, welcomeTemplate);
}
Or, using optional chaining:
function sendWelcomeEmail(user) {
const email = user?.email?.toLowerCase();
if (!email) {
console.warn('Email missing, skipping welcome email');
return;
}
sendEmail(email, welcomeTemplate);
}
Python equivalent (see also: AttributeError: 'NoneType' in Django):
# Before: KeyError if email not in user dict
def send_welcome_email(user):
email = user['email'].lower()
send_email(email, welcome_template)
# After: Safe with fallback
def send_welcome_email(user):
if not user or 'email' not in user:
logger.warning('User or email missing')
return
email = user['email'].lower()
send_email(email, welcome_template)
2. Unhandled Promise Rejections (Node.js, async JavaScript)
Node.js has a quirk: if a promise rejects and there's no .catch() handler, the entire process can crash or silently fail, becoming a ghost error.
Example:
// No catch handler: if the promise rejects, it's silently lost
async function fetchUserData(userId) {
const response = await fetch(`/api/users/${userId}`);
const user = await response.json();
return user;
}
// Called somewhere, but nobody is waiting for the promise
fetchUserData(123); // Fire and forget = disaster
What causes it:
- Missing
.catch()ortry/catchblock - Fire-and-forget async operations
- Promise chains that don't end with error handling
- Third-party SDK that rejects but you're not prepared for it
How AI fixes it:
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`API returned ${response.status}: ${response.statusText}`);
}
const user = await response.json();
return user;
} catch (error) {
logger.error('Failed to fetch user data', { userId, error: error.message });
return null;
}
}
void fetchUserData(123).catch(error => {
logger.error('Unexpected error in fetchUserData', error);
});
3. Type Mismatches and Schema Violations
You expect a string, you get a number. You expect an object, you get an array. TypeScript helps prevent this at compile time, but JavaScript and Python still suffer from it in production.
Example: JavaScript
// API returns user.age as a string "25", not a number
function canVoteInUS(user) {
return user.age >= 18; // Comparing string "25" >= 18 works, but conceptually wrong
}
function applyAgeDiscount(user) {
const discountedPrice = basePrice * (1 - user.age / 100); // NaN if age is "25" string
return discountedPrice;
}
How AI fixes it:
function canVoteInUS(user) {
const age = parseInt(user.age, 10);
if (isNaN(age)) {
throw new Error(`Invalid age format: ${user.age}`);
}
return age >= 18;
}
function applyAgeDiscount(user) {
const age = Number(user.age);
if (isNaN(age) || age < 0 || age > 150) {
throw new Error(`Age out of valid range: ${user.age}`);
}
const discountedPrice = basePrice * (1 - age / 100);
return discountedPrice;
}
4. Resource Leaks (Connections, File Handles, Memory)
Your code opens a database connection, a file, or a websocket, but doesn't close it. Over time, your application runs out of resources and crashes.
Example: JavaScript (Database)
// Connection is never closed
async function getUserById(id) {
const connection = await pool.getConnection();
const user = await connection.query(`SELECT * FROM users WHERE id = ?`, [id]);
return user[0];
// connection is never released!
}
How AI fixes it:
async function getUserById(id) {
const connection = await pool.getConnection();
try {
const result = await connection.query(`SELECT * FROM users WHERE id = ?`, [id]);
return result[0];
} finally {
await connection.release();
}
}
Python:
# Before
def read_config():
f = open('config.json')
config = json.load(f)
return config
# After: context manager
def read_config():
with open('config.json') as f:
config = json.load(f)
return config
5. Missing Error Handlers for Async Operations
You start an async operation but don't handle what happens if it fails. The operation hangs, times out, or fails silently.
Example:
app.post('/send-email', (req, res) => {
sendEmailAsync(req.body.email).then(
() => console.log('Email sent')
);
// No .catch() handler!
res.json({ status: 'ok' });
});
How AI fixes it:
app.post('/send-email', async (req, res) => {
try {
await sendEmailAsync(req.body.email);
res.json({ status: 'ok', message: 'Email sent successfully' });
} catch (error) {
logger.error('Failed to send email', { email: req.body.email, error });
res.status(500).json({
status: 'error',
message: 'Failed to send email. Please try again.'
});
}
});
6. Configuration and Environment Variable Errors
Your application depends on environment variables. At deployment, a variable is missing or misspelled, and your application crashes.
Example:
const dbUrl = process.env.DATABASE_URL;
const pool = new Pool({ connectionString: dbUrl });
// If DATABASE_URL is not set, pool is created with undefined, crashes at runtime
How AI fixes it:
function validateConfig() {
const required = ['DATABASE_URL', 'API_KEY', 'LOG_LEVEL'];
const missing = required.filter(key => !process.env[key]);
if (missing.length > 0) {
throw new Error(`Missing required environment variables: ${missing.join(', ')}`);
}
}
validateConfig();
const dbUrl = process.env.DATABASE_URL;
const pool = new Pool({ connectionString: dbUrl });
7. Off-by-One Errors and Array Index Issues
Subtle logic errors that cause you to access the wrong element, skip an element, or go out of bounds.
Example:
function getFirstNItems(array, n) {
const result = [];
for (let i = 1; i <= n; i++) { // Should start at 0, not 1
result.push(array[i]);
}
return result;
}
How AI fixes it:
function getFirstNItems(array, n) {
const result = [];
const limit = Math.min(n, array.length);
for (let i = 0; i < limit; i++) {
result.push(array[i]);
}
return result;
}
Why These Patterns Matter
These seven categories account for the vast majority of production failures. They share a few important traits:
They're mechanical. Adding a null check or handling a rejected promise follows clear rules, so these errors can be fixed consistently.
They're high-frequency. Any codebase processing real traffic will encounter these errors regularly. Each individual occurrence is minor, but collectively they consume enormous engineering time.
They're well suited to automation. Because these errors follow recognizable patterns, an AI agent can read the stack trace, write a small fix, and have your CI check it. Automated bug fixing is one part of autonomous software maintenance. To learn how these systems work in practice, read our guide to building self-healing software.
The complex bugs, the ones requiring deep domain knowledge or architectural changes, still need human engineers. The mechanical errors that fill your error logs and burn your on-call engineers' time can arrive with a fix already drafted, as a pull request you review.
bugstack captures uncaught server-side errors through an SDK (JavaScript/TypeScript, Python, Ruby, Go) and sends the fix as a pull request you control. Bugs that fail silently, without throwing, are not something it catches today. It also watches your connected host and your repository, with three autonomy levels per repository. bugstack is in early access. Request early access.