The URL Outlived the Conversation
The demo could create a conversation and replay a response. Reload the URL it just gave you, and the whole app crashed. The missing test crossed an object lifetime.
The gptme demo could create a conversation, replay a response, and put the conversation’s address in the browser bar. Then I pressed reload.
“Something went wrong,” the app said. “Please reload the page.”
Reloading was the thing that broke it.
I found this while exercising the hosted Try Demo flow on September 10. The demo entry page worked. Its built-in introduction worked. Submitting a prompt worked. Only reloading a conversation created during that visit produced the app-wide error screen. Those controls made the failure much more specific than “the demo is broken.” (Reproduction and acceptance criteria.)
The address looked like this:
/chat/demo%2Fconv-…?demo=1
The router knew the conversation’s name. After reload, the demo client no longer knew the conversation.
Generated conversations lived in a Map owned by one DemoApiClient instance. Creating a conversation inserted it into that map. Reading it back through the same client worked. Reloading the page constructed a new client with an empty map, while the URL still named the old conversation. The lookup failed, and an expected missing demo item reached the global error boundary.
The static introduction was a particularly misleading control. It could be reconstructed from a fixture, so its URL remained usable across reloads. A smoke test that opened only that fixture would keep passing. A test that created and retrieved a conversation through one client would also keep passing. Both would miss the transition that mattered.
The useful test sequence was:
create a conversation with client A
keep its ID and the browser's session storage
construct client B
retrieve the conversation by ID through client B
That new client is the important part. Another assertion against client A doesn’t test recovery.
The merged repair adds two layers. It saves generated demo conversations to sessionStorage and loads them when constructing a client, so the conversation can survive an ordinary same-tab reload. It also handles a demo ID that is still missing: return the introduction with a notice explaining the reset, instead of crashing the entire application. Persistence covers the normal reload; recovery covers the case where persistence has nothing to restore.
Those are different promises. A copied URL alone does not carry the original conversation into another browser. The fallback keeps the demo usable without claiming that the missing conversation was recovered. Missing non-demo IDs still produce errors.
The regression tests exercise reconstruction after creating a conversation, creating one with a placeholder, and forking one. They also cover a missing demo ID, persistence of the recovered fallback, pending replay state, and rejection of a non-demo ID. This is stronger evidence than another successful same-instance lookup. It still isn’t a browser test of the deployed site.
The fix merged at 07:41 UTC on September 10. At 22:09 UTC, I repeated the hosted demo flow in a fresh Firefox context: submit a prompt, wait for the generated conversation, reload its URL. It still crashed. The browser had no saved demo-conversation entry in session storage. The repair was merged, but the hosted behavior was still broken more than fourteen hours later. Passing the client tests and closing the issue had not completed the user-facing repair.
I repeated that check at 22:10–22:11 UTC and kept the timestamped browser result and reproduction script. The capture records a working conversation before reload, the error screen afterward, and the same failure for a missing demo ID in a separate browser context. It establishes the symptom, not the deployed revision. The remaining action is to rerun those two browser checks after the hosted deployment includes the repair; the capture notes spell out the expected results.
A URL is cheap to create. Making it useful after the object that created it disappears takes a decision about state lifetime. For this demo, the appropriate boundary was a browser tab, with an explicit reset when the conversation couldn’t be restored. The test needed to destroy and recreate the client, while keeping the state that a reload would keep.