DEV Community

Matt Tanner
Matt Tanner

Posted on

How to use Oracle to find and fix bugs in your code

Complex bugs often hide in the interactions between different parts of your system, making them difficult to spot through traditional debugging methods. The Oracle excels at systematically analyzing code paths, identifying issues that may only surface under specific conditions or involve subtle logic errors.

This guide demonstrates how to use the Oracle to identify and resolve a complex bug that only appears under specific circumstances.

Our example: Task API with intermittent failures

Imagine that we have a task management API, and we've been getting reports of intermittent failures when users try to complete tasks. The bug occurs only occasionally and appears to be related to concurrent usage. Here's the task completion logic that's causing issues:

// server.js \- Task completion with a subtle bug  
let tasks \= \[\];  
let completedCount \= 0;

app.patch('/tasks/:id/complete', async (req, res) \=\> {  
  const taskId \= parseInt(req.params.id);  
  const task \= tasks.find(t \=\> t.id \=== taskId);  

  if (\!task) {  
    return res.status(404).json({ error: 'Task not found' });  
  }  

  if (task.completed) {  
    return res.status(409).json({ error: 'Task already completed' });  
  }  

  // Update task status  
  task.completed \= true;  
  task.completedAt \= new Date().toISOString();  

  // Send notification (async operation)  
  sendNotification(task.userId, \`Task "${task.title}" completed\`);  

  // Update global counter  
  completedCount++;  

  // Update user's completion streak  
  await updateUserStreak(task.userId);  

  res.json(task);  
});

async function updateUserStreak(userId) {  
  const userTasks \= tasks.filter(t \=\> t.userId \=== userId && t.completed);  
  const user \= await getUserById(userId);  

  // Calculate streak: consecutive days with completed tasks  
  const today \= new Date().toDateString();  
  const yesterday \= new Date(Date.now() \- 86400000).toDateString();  

  const todayTasks \= userTasks.filter(t \=\>   
    new Date(t.completedAt).toDateString() \=== today  
  );  
  const yesterdayTasks \= userTasks.filter(t \=\>   
    new Date(t.completedAt).toDateString() \=== yesterday  
  );  

  if (todayTasks.length \> 0\) {  
    if (yesterdayTasks.length \> 0 || user.currentStreak \=== 0\) {  
      user.currentStreak \= (user.currentStreak || 0\) \+ 1;  
    }  
  }  

  await saveUser(user);  
}
Enter fullscreen mode Exit fullscreen mode

The problem: Inconsistent user streaks

Users are reporting that their task completion streaks are sometimes incorrect - they complete tasks every day, but their streak counter resets unexpectedly or displays incorrect numbers. The issue occurs more frequently when multiple users attempt to complete tasks simultaneously. Let's use the Oracle to analyze the task completion logic. Here's our prompt to Amp that will invoke the Oracle to check this out:

I have a bug where user completion streaks are getting calculated incorrectly when users complete tasks. The streak counter sometimes resets to the wrong values or doesn't increment properly. This seems to happen more during peak usage times when multiple users are completing tasks simultaneously. Use the oracle to analyze the task completion logic and identify potential race conditions or logic errors that could cause streak calculations to be wrong.

Once we send the prompt through, we can see that Amp shows it is consulting the Oracle:

Amp oracle

Oracle analysis: Identifying the root cause

The Oracle then returns with its analysis and lets us know what the issues are:

Amp Oracle analysis

With this feedback, it’s time for Amp to get to work on fixing the issue.

Using Amp to implement the fix

Now let's ask Amp to fix the identified issues:

The Oracle identified flawed logic in a few areas of the application. Use the feedback from the Oracle to fix and test the logic to make sure it works as intended and no longer has race conditions present.

Amp then goes to work, implementing fixes for the various issues that the Oracle identified. You can see the fixes being made by checking out the updated todos:

Amp todos

And we can see that Amp is changing the code accordingly based on the Oracle’s feedback:

Amp oracle feedback

Then, Amp creates a test script to verify that its changes handle concurrent updates without race conditions.

Amp fixing bug

And lastly, it gives us a final confirmation message of the fixes, including a detailed breakdown of the key fixes applied and test results:

Amp breakdown of fixes

With just a few steps, we’ve had the Oracle debug and diagnose the race condition that we inadvertently introduced into our code. Then, we used this feedback to get Amp to fix the issue and confirm it through a series of generated tests.

Oracle analysis for different bug types

The Oracle's systematic analysis approach works well for various bug categories:

  • Concurrency issues: Race conditions, deadlocks, shared state problems
  • Logic errors: Edge cases, incorrect condition handling, state machine bugs
  • Memory problems: Leaks, inefficient cleanup, unbounded growth
  • Integration bugs: API contract mismatches, timing-dependent failures
  • Performance issues: Inefficient algorithms, resource contention

Best practices for bug analysis with Oracle

When it comes to using the Oracle for bug analysis, there are some best practices that can help you get the most out of it. These tips include:

Be specific about symptoms: Include error messages, reproduction steps, and timing information

Provide relevant context: Share related code, configuration, and environment details where the bug occurs

Focus on complex interactions: Use Oracle for bugs involving multiple components or timing dependencies

Ask for a systematic analysis: Request a step-by-step breakdown of potential failure modes

Follow up with implementation: Use Amp to implement Oracle's recommended fixes immediately

When to use Oracle for debugging

Although you can use the Oracle whenever you want, certain scenarios make more sense than others. For example, ideal scenarios for invoking the Oracle include:

  • Intermittent bugs that are hard to reproduce
  • Issues that only appear under load or specific conditions
  • Complex multi-component failures
  • Race conditions and timing-related problems

On the other hand, bugs that include:

  • Simple syntax errors or obvious logic mistakes
  • Issues with clear stack traces pointing to specific lines
  • Problems that are easily reproduced and debugged locally

These are less ideal for the Oracle and can likely be handled manually or through Amp’s main agent.

By using the Oracle in Amp, you can transform mysterious, intermittent bugs into well-understood problems with clear solutions. Using this tool for complex bugs can transform hours of trial-and-error debugging into an efficient process that requires only a few minutes.

Top comments (0)