The problem
After the desktop app sat idle for about an hour, uploads failed. People saw a stuck “uploading” dialog, a “session expired” message and, in the AI research feature, a lost result after the research was done. Closing and reopening the app fixed it. The ticket was opened in January 2026.
What had been tried
Five fixes shipped over about nine months. Each was judged by waiting an hour and trying again. Nothing was measured before or after, nothing had a test, and nothing reported the failure to error tracking. So the bug could return unseen, and a ticket could close without a recorded confirmation.
What I measured
I recorded a baseline first: 10 uploads of a small file and 5 of a 12 MB file, timed from the click to done. Seven requests ran one after another before every upload, including a throwaway storage call made only to refresh a cached value.
The failure itself needs an hour to appear, so I reproduced it deterministically instead. I ran the real library code with the network stubbed and changed one thing at a time.
The root cause
In the storage library version we used, reads, lists, removes and copies refresh credentials before they run. An upload does not. It signs with a saved copy left by whichever call ran last, and those credentials last one hour. That explains the pattern: uploads fail after a long idle but work straight after viewing a document, because viewing refreshes the copy.
What I changed
Every upload now asks for fresh credentials when it signs, with one retry for a true expiry. The old workaround is gone and all four upload paths share one module. A rejected credential that is not an expiry now checks the session and shows the sign-in prompt, not a generic failure. Failures go to error tracking with codes and counts only, with storage addresses redacted.
I added a contract test that runs the real library with only the network stubbed, and a canary test that fails when a library upgrade fixes the behaviour, so the workaround can then be removed.
The result
On development, with the same script, a small upload went from a median of 876 ms to 546 ms, and requests before the upload fell from seven to four. One upload after about 70 minutes idle succeeded first time. The fix was released to production in app version 1.18.1 on 7 October 2026.
What I have not shown
The measurements are from development with one test user, and the idle check is one run. Neither the product owner nor the error tracking data has confirmed the fix in production yet, and the alert rule for retries is still to be created.
What I would do differently
Start with a recorded baseline and end with the same measurement repeated. Waiting an hour and trying again is not evidence, and it is why the earlier fixes did not hold.