Fixing Internet Research That Wasn’t Saving to Auto Writer Jobs

The bug I hit while building the auto writer

I hit one of those bugs that looks small from the outside but touches half the system once you start pulling on it.

The auto writer had a research step where it could run internet research before generating an article. The research panel worked. The search results came back. The notes were visible in the UI. The generated article could even use the research during the current session.

Then I opened the saved job later and the research was gone.

Not gone from the browser state. Not gone from the temporary generation flow. Gone from the actual job record. That meant the tool could appear to work while I was sitting in front of it, but the moment the job moved into a queued, saved, or reopened state, the internet research disappeared.

That is the kind of bug that makes an automation tool feel unreliable. So I stopped adding new features and fixed the persistence path properly.

How the auto writer job flow was supposed to work

The intended flow was pretty simple:

  • The user creates an auto writer job.
  • They enter the article topic, title, target length, tone, and any extra instructions.
  • If internet research is enabled, the app runs a research step before article generation.
  • The research output gets attached to the job.
  • The writer uses that stored research when generating the article.
  • When the job is reopened, the same research should still be visible.

That last part was the broken piece. The research existed during the live request, but it was never being saved back into the job in a reliable way.

At first, I thought this would be a quick missing field fix. Maybe I forgot to include research in the update payload. That was partly true, but not the whole story.

The first clue: the UI was telling a different story than the database

The browser made the feature look finished. I could trigger research, see the search summary, and watch the writing agent use that content. So naturally, my first instinct was to blame the job detail page for not loading it back in.

I opened the database record directly. No research.

That changed the whole shape of the problem. The job detail page was not failing to display the research. The research was never making it into the stored job in the first place.

Once I knew that, I stopped poking at the frontend display layer and traced the path from the research runner to the job save call.

Tracing the data path

I followed the research data through each step like a package moving through a warehouse. This is usually where the real bug shows itself.

StepExpected behaviorWhat was actually happening
Research requestRun internet research for the job topicWorked fine
Research responseReturn summary, sources, and extracted notesWorked fine
UI stateShow research in the job builderWorked fine
Job payloadAttach research before save or queuePartly missing
Database recordStore research with the jobBroken
Worker processLoad research from the saved jobCould not load what was never saved

The failure was between UI state and job payload. The data lived in one part of the app, while the save function was reading from another.

Classic vibe coding trap. I had moved fast and built the working experience first. The research panel got its own local state, and the job form had its own job object. They looked connected because the screen made them feel connected. Underneath, they were not fully wired together.

The real issue: research was session data, not job data

The bug came down to this: internet research was being treated like temporary session data.

The research runner returned a result and stored it in the active page state. That state was then passed into the immediate article generation call. So if I researched and generated in one straight path, everything seemed fine.

But the saved job object did not own the research. It had fields for title, topic, style, category, target word count, and generation settings. It did not have a clean, consistent place for internet research output.

That meant queued jobs, reopened drafts, retries, and background workers were all missing the same piece of context.

The fix was not to patch the display page. The fix was to make research a first-class part of the job model.

Adding a proper research field to the job

I added a dedicated field for research data on the job record. I wanted it to hold more than a single blob of text, because the writer needs different kinds of context later.

The structure I landed on looked roughly like this:

  • enabled: whether internet research was turned on for the job
  • query: the search query or generated research prompt
  • summary: the condensed research brief
  • notes: extracted facts, angles, and supporting points
  • sources: source titles and citation metadata, when available
  • createdAt: when the research was generated
  • status: completed, failed, skipped, or pending

I kept this as structured JSON instead of flattening it into several separate columns. That gives the feature room to grow without needing a database migration every time I add a small research detail.

The article generator does not need to know how the research was gathered. It only needs a clean research package attached to the job. That separation made the next fixes easier.

Wiring research into the save payload

Once the job had a proper place for research, I had to make sure every save path used it.

This is where I found another sneaky problem. There were multiple ways a job could be saved:

  • Manual draft save
  • Save and generate now
  • Save and queue for later
  • Retry failed job
  • Duplicate an existing job

The research field had to survive all of them.

The first version of the fix only patched the “generate now” path. That made my test pass once, then fail when I queued the same type of job. Annoying, but useful. It forced me to stop thinking about buttons and start thinking about one shared job serializer.

So I pulled the payload creation into one helper. Every action now builds the job payload through the same function. If research is present in the builder state, it gets attached. If research is disabled, the job records that too, instead of leaving the field ambiguous.

That one move cleaned up more than this bug. It also made the job creation code easier to reason about.

Making the background worker read from the saved job

After the save path was fixed, I checked the worker.

The worker used to accept research as part of the live generation request. That worked for immediate jobs but not for queued jobs. Queued jobs should not depend on whatever was sitting in browser memory when the user clicked a button.

I changed the worker so it loads the job from storage, reads the research package from that saved job, and then passes it into the writing prompt.

That sounds obvious now, but it was the missing mental model. The saved job is the source of truth. Not the UI. Not the request body from five seconds ago. Not a temporary object inside the page component.

The saved job has to contain everything needed to run later, run again, or run somewhere else.

That became the rule for this part of the system.

The migration and fallback handling

Because existing jobs were created before this field existed, I needed a safe fallback. I did not want older jobs to break just because they did not have a research object.

The migration added the new field with a default empty value. On the app side, I added a normalizer that turns missing or old research data into a predictable shape.

So instead of checking for five different versions of “nothing,” the rest of the app gets one clean structure:

  • enabled: false
  • status: "skipped"
  • summary: ""
  • notes: []
  • sources: []

This made the UI calmer too. The job detail page no longer has to guess whether research is missing because it failed, was skipped, or came from an older record. It can show the right state.

The fix sequence

Here is the actual order I followed once I understood the shape of the bug:

  1. Confirmed the research existed in browser state after the internet research step.
  2. Checked the saved job record and confirmed the research was missing there.
  3. Added a structured research field to the job model.
  4. Updated the job serializer so all save actions include research when present.
  5. Patched queued jobs to read research from the saved job, not from live request state.
  6. Added fallback handling for older jobs without research data.
  7. Tested draft save, generate now, queue, retry, and duplicate flows.
  8. Reopened completed jobs to confirm the research panel still had the saved context.

The most useful part of that list was testing every path that creates or reuses a job. The first pass only fixed the happy path. The second pass fixed the product.

What broke during the fix

A few things went sideways while I was patching this.

First, I accidentally saved an empty research object over completed research when toggling the internet research setting off and on. That happened because the toggle handler reset too much state. I changed it so disabling research marks it as skipped, but does not destroy prior research until the user runs a new search or clears it on purpose.

Second, duplicated jobs inherited the full research package, including the old timestamp. That was technically fine, but confusing. I decided duplicated jobs should copy the research content but reset the status to indicate it was copied from the source job. That way I can show a small note in the UI later if needed.

Third, queued jobs revealed a timing issue. If research was still running and the user clicked queue, the job could save before the research result came back. I fixed that by disabling the queue action while research is pending, then showing a clear “research in progress” state. Simple guard. Big difference.

Testing the fix like a real user

I did not only test this from the database side. I ran through the flows the way I would use the tool when writing content.

  • Create a job, run research, save as draft, refresh, reopen.
  • Create a job, run research, generate immediately, inspect the completed job.
  • Create a job, run research, queue it, process it from the worker.
  • Retry a failed job and confirm the same research is still available.
  • Duplicate a researched job and confirm the copied job has usable research.
  • Create a job with research disabled and confirm the app does not invent empty junk.

The refresh test is my favorite for bugs like this. If the data survives a full page reload, there is a good chance it is actually persisted and not just living in a happy little frontend bubble.

Small UI changes that made the fix feel complete

Once the backend behavior was fixed, I added a few UI details so the feature felt trustworthy.

The research panel now shows whether the research is saved, pending, copied, skipped, or failed. I also added a saved timestamp after the research is attached to the job. That tiny label helps a lot when debugging, and it gives the user confidence that the system kept their context.

I also made the job detail page render the stored research in read-only mode for completed jobs. Before this, the research panel only felt like part of the creation flow. Now it feels like part of the job history.

That matters because research is not just input. It explains why the article came out the way it did.

The bigger lesson from this bug

This fix reminded me of a pattern I keep running into while building with speed: if a feature affects the output of a background job, it cannot live only in the frontend.

Browser state is great for interaction. It is not a source of truth. Once a job can be queued, retried, duplicated, or reopened later, the saved record needs to carry the full instruction set.

For this auto writer, that means the job should include the topic, title, settings, voice instructions, formatting rules, research, and any other context the writer needs to produce the same result later.

The moment I started treating the job as a portable package instead of a form submission, the architecture got cleaner.

What I would do earlier next time

If I were building this piece again, I would define the job payload before building the UI panels. Not in a heavy planning-doc way. Just a plain contract that says, “Here is everything this job needs to run without the browser.”

That would have exposed the missing research field sooner.

I would also add persistence checks as soon as a feature crosses into async territory. If a worker needs it, save it. If a retry needs it, save it. If the user expects to see it tomorrow, save it.

This was not a flashy fix, but it made the tool feel much more solid. Internet research now persists to the auto writer job, queued generation has the same context as live generation, and reopened jobs finally show the research that shaped the article.

Good little win. On to the next bug.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top