Engineering Practice for Fixing a Long-Standing Bug with a General-Purpose AI Coding Assistant: A 9-Minute Workflow from Reproduction to Verification
This article describes how a team used a general-purpose AI coding assistant to diagnose and fix a long-standing concurrency bug in a content distribution tool. It covers how to prepare minimal context, describe the defect in observable terms, require reproduction before patching, identify the root cause, redesign task state transitions with atomic conditional updates, and verify the fix through repeatable concurrency tests. Readers will learn a practical workflow and engineering principles for making AI coding assistants genuinely useful in complex system maintenance.
1. Background: A Stubborn Defect That Remained Unresolved
In a self-developed content distribution tool, a long-standing defect had never been fully resolved. The tool collects content from multiple data sources, then cleans, deduplicates, classifies, and schedules it before distributing it to various downstream systems. The defect manifested as follows: under certain concurrency conditions, some tasks were distributed twice or missed entirely, and the issue occurred infrequently, making it hard to reproduce reliably.
The team had already tried several conventional troubleshooting approaches, including:
- Checking distributed locks and idempotency logic;
- Adjusting the order of task state transitions;
- Adding database unique constraints;
- Adding log instrumentation at key points;
- Asking different AI coding assistants to read the relevant code and suggest changes.
But these attempts either failed to locate the root cause or, after being fixed, could still reproduce the problem under stress testing. Because the defect directly affects downstream content consistency, it remained a high-risk item in maintenance work despite its low trigger probability.
2. Goals and Acceptance Criteria
Before revisiting the defect, the team clarified the goals and acceptance criteria for this engineering effort to avoid a situation where the fix “looks correct but ships with hidden defects.”
2.1 Goals
In a minimal reproducible scenario, locate the root cause of the defect, complete the code changes, and confirm through automated verification that the problem no longer occurs.
2.2 Acceptance Criteria
The fix needed to satisfy all of the following conditions:
- Reproducibility: The original defect can be triggered with deterministic steps;
- Clear root cause: The problem is not masked by adding retries, delays, or fallback logic;
- Controlled impact: The changes do not break normal task paths;
- Verifiability: Repeatable verification scripts or test cases are provided;
- Explainability: The rationale for the changes can be expressed clearly in natural language and code comments.
3. Task Breakdown: Making an AI Coding Assistant Truly Usable
An AI coding assistant is not a “universal fixer.” If you simply throw “fix this bug for me” at it, you will rarely get a satisfactory result. The effective approach is to manage it like a newly onboarded engineer: provide sufficient context, define clear boundaries, and accept deliverables step by step.
3.1 Step One: Prepare Minimal Context
The defect-related code, log snippets, database table schemas, and task state machine were organized into a standalone context package. The content was kept within the necessary scope, avoiding dumping the entire repository at once.
The context package included:
- The task table schema;
- Task status enum definitions;
- Core functions for creating tasks and updating status;
- Logs printed when duplicate distribution occurs;
- Downstream callback acknowledgment logic;
- A description of known concurrency trigger scenarios.
3.2 Step Two: Describe the Defect in Natural Language
When describing the defect, focus on observable behavior rather than presupposing a cause. For example:
When 20 concurrent requests create the same batch of tasks simultaneously, a certain percentage of tasks are dispatched twice. The logs show that both dispatches read the “PENDING” status, but both status updates returned success.
This description helps the model focus on the concurrency window between status checking and status updating, rather than reading all the code aimlessly.
3.3 Step Three: Require Reproduction Before Modification
Explicitly instruct the AI coding assistant not to modify the code directly, but first to provide a reproduction path and a root cause hypothesis. Only after reproduction succeeds is it allowed to propose a fix patch.
4. Root Cause Analysis
After reading the context, the AI coding assistant made a key judgment: there is a lack of atomicity between the task status check and the status update. The simplified code logic is as follows:
def dispatch(task_id):task = get_task(task_id)if task.status != "PENDING":returnsend_to_downstream(task)task.status = "DISPATCHED"save_task(task)
In concurrent scenarios, multiple execution units may read the PENDING status at the same time, then all pass the check, ultimately causing duplicate dispatch. Although a database-level unique constraint exists, it cannot prevent the second dispatch from already having occurred.
Further inspection revealed that although distributed locks were introduced in the code, the lock granularity was based on the channel the task belongs to rather than the task itself. When different tasks under the same channel are scheduled concurrently, the lock cannot prevent the same task from being processed by multiple execution units simultaneously.
The root cause can therefore be summarized in three parts:
- The status check and update are not atomic operations;
- The lock granularity does not match the business conflict granularity;
- There is no “in-progress” intermediate state or conditional update mechanism.
5. Fix and Code Changes
After confirming the root cause, the AI coding assistant proposed a fix. The core idea was not to add retries, but to change the state transitions into conditional updates with idempotent semantics.
5.1 Introduce an Intermediate State
Add a DISPATCHING state between PENDING and DISPATCHED. A task is first atomically updated from PENDING to DISPATCHING, and only the execution unit that successfully performs this update is authorized to proceed with the downstream dispatch.
PENDING -> DISPATCHING -> DISPATCHED
5.2 Use Conditional Updates
Change the ordinary update into an update with a precondition:
def mark_dispatching(task_id):sql = """UPDATE taskSET status = 'DISPATCHING', updated_at = now()WHERE task_id = ? AND status = 'PENDING'"""affected = execute_sql(sql, task_id)return affected == 1
Only the execution unit whose affected row count is 1 may continue with the dispatch; an affected row count of 0 means the task has already been claimed by another unit, so it returns directly.
5.3 Add Exception Rollback
If the downstream dispatch fails, the status needs to be rolled back from DISPATCHING to PENDING. But the rollback must be conditional to avoid overwriting status updates made by other execution units:
def rollback_dispatching(task_id):sql = """UPDATE taskSET status = 'PENDING', updated_at = now()WHERE task_id = ? AND status = 'DISPATCHING'"""execute_sql(sql, task_id)
5.4 Keep the Lock as a Safety Net
The existing distributed lock does not need to be removed entirely; instead, adjust the lock granularity to the task level. This way, even if other concurrency paths are introduced in the future, a second layer of protection is provided at the application level.
6. Verification: From Reproduction to Regression
A completed fix does not mean the problem is solved; it must go through a repeatable verification process.
6.1 Build a Minimal Reproduction Case
Write a concurrency test script that simulates 20 execution units processing the same batch of tasks simultaneously. Each execution unit performs the following logic:
def worker(task_id):acquired = mark_dispatching(task_id)if not acquired:returntry:send_to_downstream(task_id)mark_dispatched(task_id)except Exception:rollback_dispatching(task_id)
6.2 Assert Downstream Receipt Count
The test records how many times each task is sent downstream and asserts that every task is dispatched exactly once.
6.3 Run Regression Tests
Add the concurrency test to the existing regression suite, and supplement it with failure rollback scenarios:
- Downstream timeout;
- Downstream returns failure;
- Execution unit crashes;
- Database update fails.
In the above scenarios, confirm that tasks do not end up stuck in the DISPATCHING state and can return to PENDING to wait for retry or enter the exception queue.
6.4 Time Consumption
The entire interaction process lasted approximately 9 minutes. Most of that time was spent waiting for the assistant to analyze the code, generate fix suggestions, and run verification scripts, rather than manually modifying code. From reproduction to final confirmation of verification success, human subjective intervention was limited to confirming the fix approach and final acceptance.
7. Engineering Lessons
The value of this practice lies not in “how powerful the model is,” but in whether the process design is sound.
7.1 Context Design Matters More Than Model Capability
Providing a clear, minimal, and observable defect description is the prerequisite for obtaining effective fix suggestions. The closer the context information is to the real failure scene, the easier it is for the assistant to locate the concurrency window.
7.2 Require “Prove Before Modify”
If you let the assistant modify code directly, it may produce a seemingly reasonable but unverifiable solution. Requiring it to first explain the root cause and construct a reproduction case before providing a patch can significantly reduce the probability of “fake fixes.”
7.3 Acceptance Criteria Must Be Defined Upfront
Defining “what counts as fixed” before the fix can avoid endless trial and error later. Concurrency defects especially require “each task is dispatched exactly once” as an assertable condition.
7.4 General Configuration of Cloud Engineering Capabilities
In actual production environments, such tasks typically run on cloud hosts or container platforms and rely on general cloud capabilities such as database services, logging services, monitoring and alerting, and message queues. Regardless of which cloud provider is used, the core engineering approach is the same:
- Persist task state to a database that supports transactions;
- Use conditional updates to ensure idempotent state transitions;
- Turn concurrency conflicts into observable metrics;
- Add automatic rollback and alerting to exception paths.
For enterprise users, these general capabilities can be implemented on mainstream cloud infrastructure. For example, deploy the scheduling service on a container platform in the cloud, store task state in a database service, collect exception rollback records through a logging service, and observe the failure ratio of PENDING -> DISPATCHING through monitoring and alerting. As long as the design is sound, task distribution accuracy can be maintained even during transient concurrency peaks.
8. Conclusion
This practice compressed the fix of a long-standing stubborn defect into approximately 9 minutes. The key was not pinning hopes on “the magical capability of a particular AI tool,” but decomposing the engineering problem into clear context, strict verification standards, and a repeatable regression process.
Truly effective AI coding practice requires developers to have the following abilities:
- Accurately describe the failure phenomenon;
- Reasonably trim the context;
- Design a verifiable fix;
- Build regression test cases;
- Maintain engineering judgment over the assistant’s output.
When these abilities are combined with an AI coding assistant, it is no longer just a chat window, but an engineering collaborator that can review code, explain root causes, generate patches, and assist with verification on demand. For teams maintaining complex systems over the long term, the efficiency gains from this way of working are far more valuable than simply “writing more code.”
