DEV Community

Cover image for When an Ordinary Workflow Needs a Hack, Ask AI to Step Back
Robin Shen
Robin Shen

Posted on

When an Ordinary Workflow Needs a Hack, Ask AI to Step Back

Coding agents are remarkably good at finding the next plausible fix. If a cookie is not removed, they adjust the cookie handling. If two browsers behave differently, they investigate browser behavior. If a result depends on timing, they add a delay.

Every step can be reasonable. The danger is that a long chain of reasonable patches can lead to a solution that works without ever addressing the real problem.

We ran into exactly this while migrating onedev.io from an older web framework to Next.js. Signing out—one of the most ordinary actions on any website—worked only occasionally. After several rounds of debugging, a short delay made it reliable.

The delay solved the symptom. It also revealed that we were asking the wrong question.

A Routine Feature Became Mysteriously Unreliable

I should first explain my role in this story: I am completely unfamiliar with React and Next.js. I relied entirely on a coding agent to migrate the website and investigate the problem.

I could describe what I saw, test changes, and decide whether a result felt acceptable. I could not tell the agent which framework feature to use or which part of the architecture to inspect. That is, after all, why I delegated the work.

The visible problem was simple. Clicking Sign Out returned me to the home page, but I could sometimes still open pages reserved for signed-in users—even after a hard refresh.

The ordinary Sign Out action that exposed the deeper design problem

The behavior was inconsistent:

  • It sometimes worked in Safari but not Chrome.
  • Clearing all cookies helped only temporarily.
  • Opening browser developer tools made it more likely to work.
  • Some fixes improved the success rate without making it reliable.

These clues made the browser look suspicious. But the browser was only where the problem became visible.

Reasonable Debugging Slowly Turned Into Patchwork

The website sits behind several layers: the browser connects through Cloudflare, Cloudflare connects to nginx, and nginx forwards the request to the application. It was natural to suspect that one of those layers was confusing secure and non-secure cookies.

We checked the public URL, forwarded connection information, cookie names, Cloudflare features, and old browser data. We also tried making the sign-out code more explicit:

  • Delete every possible form of the session cookie.
  • Create a dedicated sign-out endpoint.
  • Exclude that endpoint from global authentication handling.
  • Change click handling in case navigation started too early.
  • Wait for the server response before returning to the home page.

Each attempt was based on a real observation. None was foolish. But the growing list was itself an important observation: a routine operation was accumulating special handling.

Eventually, this worked reliably:

<p>Signing out…</p>
<script>
  setTimeout(() => location.replace("/"), 250);
</script>
Enter fullscreen mode Exit fullscreen mode

The application showed a brief “Signing out…” page, waited 250 milliseconds, and then went home.

Problem solved?

Technically, yes. The website behaved correctly. But a normal sign-out should not depend on an arbitrary pause. Why 250 milliseconds? Would 100 be enough? Would it still work on a slower connection? Why should opening developer tools change anything?

The delay was not a satisfying answer. It was evidence that two things were happening in the wrong order.

The Most Useful Prompt Was Not Technical

At this point, I did not give the agent a detailed React or Next.js diagnosis. I could not have done so. I simply challenged the result:

This is a very common workflow. It should not require a timing hack. Please revisit the relevant code, check whether the implementation follows common conventions and best practices, find the root cause, and remove the workaround.

That prompt changed the direction of the investigation.

Until then, the work had focused on making the failing sign-out action more robust. Now the agent stepped back and reviewed the whole authentication workflow: where a session was created, where it was checked, where it could be refreshed, and where it was removed.

This is an important distinction in vibe coding. You do not need to know the names of all the framework components. You need to recognize when the shape of a solution feels wrong and ask the agent to widen its view.

The Real Problem Was Outside the Sign-Out Code

The website had been migrated from an older stack. My main instruction during that migration was to preserve existing behavior while changing the framework. That was a sensible requirement, but it encouraged the new implementation to carry forward some old, request-wide behavior.

In the migrated site, nearly every page request passed through a global authentication wrapper. In simplified form, it looked like this:

export default auth((request) => {
  // Prepare information used by the migrated website.
  // Authentication also runs for nearly every request.
  return NextResponse.next();
});
Enter fullscreen mode Exit fullscreen mode

The wrapper was convenient because it made authentication information available everywhere. But the application already checked access again around the pages that actually required a signed-in user.

In plain language, two parts of the application had taken responsibility for the same thing.

That duplication caused a race:

  1. The sign-out request told the browser to remove the session.
  2. A nearby page request passed through the global wrapper and refreshed that same session.
  3. Whichever response the browser processed last decided whether the user appeared signed in or signed out.

This explained all the strange behavior. Different browsers, developer tools, and the 250-millisecond delay changed the timing. They changed which response won, but they did not create the underlying problem.

The Structural Fix Made the Workaround Disappear

The fix was to give each part of the application one clear job.

The global request handler kept only the small piece of migrated page information it genuinely needed to provide:

export default function proxy(request: NextRequest) {
  const headers = new Headers(request.headers);
  headers.set("x-pathname", request.nextUrl.pathname);

  return NextResponse.next({
    request: { headers },
  });
}
Enter fullscreen mode Exit fullscreen mode

Pages that require a signed-in user continued to protect themselves. The authentication library returned to handling sign-out through its normal built-in function:

export async function logout() {
  await signOut({ redirectTo: "/" });
}
Enter fullscreen mode Exit fullscreen mode

Then we removed the custom endpoint, manual cookie deletion, special click handling, and 250-millisecond delay.

Sign-out worked reliably in both Chrome and Safari.

The final code was not only more correct; it was much simpler than the patched version. That is often a strong sign that the investigation has reached the real cause.

What This Teaches Us About Vibe Coding

Vibe coding does not mean accepting whatever code happens to work. Nor does it require the user to become an expert in every framework the agent touches.

The user brings something different but equally important: judgment about the expected shape of the result.

You may not know how session cookies work, but you know that signing out is routine. You may not know the framework’s routing system, but you know that opening developer tools should not fix production behavior. You may not know which module owns a task, but you can see when a simple feature has collected delays, retries, duplicated logic, and browser-specific branches.

Those are moments to stop asking for another local fix.

A useful, technology-neutral prompt is:

This is a common workflow, but the solution feels like a hack.
Please step back and review the whole workflow. Check the relevant code
against the framework's common conventions and best practices, find the
root cause, simplify the design, and remove the workaround.
Enter fullscreen mode Exit fullscreen mode

Notice what this prompt does not contain. It does not list framework APIs. It does not guess at the root cause. It does not tell the agent how to implement the fix. The user identifies the mismatch between an ordinary requirement and an extraordinary solution; the agent supplies the technical investigation.

Use Patches as Evidence, Not Architecture

The 250-millisecond delay was not wasted work. It proved that timing mattered and helped narrow the investigation. Temporary patches are often useful experiments.

The mistake would have been leaving that experiment in place and calling the task finished.

When a workaround makes a bug disappear, ask what the workaround changed. A delay changes ordering. A retry may hide unreliable state. Manual cleanup may compensate for two components owning the same responsibility. A browser-specific branch may hide a problem that belongs on the server.

The workaround often contains a clue about the root cause. Treat it as evidence, then remove it after the design is corrected.

Make the Simple Path Simple Again

The most valuable contribution I made to this debugging session was not technical. I did not recognize the global authentication wrapper as the problem, and I did not propose the final architecture. I simply refused to believe that a routine workflow should need an arbitrary delay.

That was enough to redirect the coding agent from patching the symptom to reviewing the system.

So when vibe coding produces a solution that works but feels unnatural, do not assume that your lack of framework knowledge means you must accept it. Ask the agent to step back. Ask whether the implementation follows the framework’s normal path. Ask it to find duplicated responsibility, remove accidental complexity, and make the common case simple again.

This lesson came from onedev.io, where coding agents are becoming part of our issue, pull request, workspace, and CI/CD workflows. The principle applies far beyond authentication: working code is an important milestone, but when an ordinary feature needs an extraordinary workaround, it should not be the finish line.

Top comments (0)