fix(mobile): preserve saved work after storage read failures - #9710
Conversation
Do not treat unreadable drafts or queued records as empty storage. Stop writes and attachment cleanup until persisted owners can be read. Retain failed debounced edits for a later flush. Stop environment cleanup before deleting queued messages when its storage read fails. Created with GPT-6 Astra (preview) in Codex.
Thread transfer impact✅ Thread transfer remains within every enforced ceiling.
Baseline: Scenario and decoded snapshot size10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.
Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed. |
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — This is a focused mobile persistence bug fix that fails closed on unreadable drafts or outbox records, preventing saved work and attachment ownership from being overwritten or deleted. The new retry and cleanup behavior is bounded to storage-failure paths and covered by targeted tests. You can add or adjust custom eligibility rules. Learn more. |
## What's Changed * perf(server): use one query for buffered provider events by @t3dotgg in pingdotgg/t3code#9706 * perf(relay): avoid repeated activity decoding by @t3dotgg in pingdotgg/t3code#9708 * perf(web): stop continuous chat status animations by @t3dotgg in pingdotgg/t3code#9709 * fix(mobile): preserve saved work after storage read failures by @t3dotgg in pingdotgg/t3code#9710 * perf(web): stop replaying terminal buffers on rollover by @t3dotgg in pingdotgg/t3code#9707 * feat(web): preview pull request links by @maria-rcks in pingdotgg/t3code#9631 * perf(client): reduce thread-list update work by @t3dotgg in pingdotgg/t3code#9716 * fix(server): settle inactive threads with open PRs by @Gigioxx in pingdotgg/t3code#9610 * fix(server): bound slow-client event buffers by @t3dotgg in pingdotgg/t3code#9715 * test(server): allow either valid file-search match by @t3dotgg in pingdotgg/t3code#9720 * fix(web): match provider settings layout for disconnected devices by @flamboh in pingdotgg/t3code#9619 * fix(web): keep the slash menu above the composer when vertical space is short by @Mnigos in pingdotgg/t3code#9625 * fix(mobile): remove provider setup by @juliusmarminge in pingdotgg/t3code#9721 * perf(web): stop rendering hidden terminals by @t3dotgg in pingdotgg/t3code#9718 * fix(mobile): read file-backed image drafts before enabling them by @t3dotgg in pingdotgg/t3code#9713 * perf(server): replay only the selected thread by @t3dotgg in pingdotgg/t3code#9726 * fix(web): mute composer helper text by @jakeleventhal in pingdotgg/t3code#9654 * feat(web): unpin threads from the sidebar multi-select menu by @gsimone in pingdotgg/t3code#9651 * perf(web): reuse timeline rows while text streams by @t3dotgg in pingdotgg/t3code#9725 * fix(relay): bound stalled push requests by @t3dotgg in pingdotgg/t3code#9734 * perf(server): stop caching unused OpenCode tool parts by @t3dotgg in pingdotgg/t3code#9738 * fix(web): fold single trailing activity by @maria-rcks in pingdotgg/t3code#9739 * fix(web): show project settings for new threads by @maria-rcks in pingdotgg/t3code#9743 * perf(mobile): bound the parsed review cache by @t3dotgg in pingdotgg/t3code#9749 **Full Changelog**: pingdotgg/t3code@v0.0.39-nightly.20260904.1279...v0.0.39-nightly.20260904.1280 Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.39-nightly.20260904.1280
A failed mobile draft read could look like an empty store. The next save could overwrite saved work. An unreadable outbox record could also hide attachment owners from cleanup.
Draft saves and attachment cleanup now require successful hydration. Outbox loads no longer return a partial queue. Final flush retries pending edits after a successful read. Removing an environment now stops if its outbox cannot be read.
One unreadable outbox record blocks queue loading and cleanup until it can be read. This keeps incomplete ownership data from causing file deletion.
This is a safety prerequisite for file-backed image drafts. It keeps current inline images and storage versions. No native changes.
Verified with 115 focused tests, mobile typecheck, and targeted lint. Real-source checks with temporary files preserve draft metadata and attachment bytes after read failures or unsupported records. No device or native build ran.
Created with GPT-6 Astra (preview) in Codex.
Note
Medium Risk
Changes mobile on-device draft/outbox error handling and flush semantics; incorrect propagation could block cleanup or leave edits unsaved, but the intent is to prevent data loss from silent empty hydration.
Overview
Mobile local persistence now fails closed instead of treating a bad read as an empty store, so later saves and cleanup cannot overwrite drafts, drop attachment files, or clear queues based on incomplete data.
Thread outbox:
expoThreadOutboxStorage.load()aborts on the first unreadable.jsonrecord (no partial queue).clearEnvironmentpropagates load errors instead of proceeding with an empty persisted set.Composer drafts:
loadPersistedComposerStatethrowsComposerDraftPersistenceErroron read/decode failure (no silent empty hydration). Debounced persist failures setpersistRetryNeededsoflushComposerDraftsretries after a successful read; hydration clearsloadPromiseon failure so awaited callers can retry. Attachment cleanup and flush paths already wait for hydration and reject when disk cannot be read.Tests cover partial outbox loads, environment clear on outbox read failure, draft read/decode failures (disk unchanged, no attachment deletion), and flush retry after a transient read error.
Reviewed by Cursor Bugbot for commit d4c37f6. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Preserve saved work after storage read failures in mobile state
flushComposerDrafts.thread-outbox-manager.tsanduse-composer-drafts.tsnow reject on read failures instead of resolving empty states; existing callers must handle the rejection.Macroscope summarized d4c37f6.