Skip to content

fix(mobile): preserve saved work after storage read failures - #9710

Merged
t3dotgg merged 1 commit into
mainfrom
t3code/fix-mobile-draft-storage-guards
Sep 4, 2026
Merged

fix(mobile): preserve saved work after storage read failures#9710
t3dotgg merged 1 commit into
mainfrom
t3code/fix-mobile-draft-storage-guards

Conversation

@t3dotgg

@t3dotgg t3dotgg commented Sep 4, 2026

Copy link
Copy Markdown
Member

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 .json record (no partial queue). clearEnvironment propagates load errors instead of proceeding with an empty persisted set.

Composer drafts: loadPersistedComposerState throws ComposerDraftPersistenceError on read/decode failure (no silent empty hydration). Debounced persist failures set persistRetryNeeded so flushComposerDrafts retries after a successful read; hydration clears loadPromise on 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

  • thread-outbox-storage.ts and thread-outbox-manager.ts throw errors on persisted-record read failures instead of returning partial or empty queues.
  • use-composer-drafts.ts rejects on persisted-state load failures instead of returning an empty state that overwrites existing drafts.
  • Composer draft hydration clears the cached load promise on failure so later calls retry, and failed debounced persistence attempts are retried by flushComposerDrafts.
  • Risk: Load and hydration methods in thread-outbox-manager.ts and use-composer-drafts.ts now reject on read failures instead of resolving empty states; existing callers must handle the rejection.

Macroscope summarized d4c37f6.

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.
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Sep 4, 2026
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 13.6 KiB 13.7 KiB +172 B (+1.2%) 15.1 KiB
Codex Thread snapshot wire 7.0 KiB 7.0 KiB −4 B (−0.1%) 7.3 KiB
Codex Live turn WebSocket wire 6.5 KiB 6.7 KiB +176 B (+2.6%) 7.8 KiB
Codex Live turn WebSocket decoded 57.0 KiB 58.5 KiB +1.5 KiB (+2.6%) 66.4 KiB
Codex Live turn messages 9 10 +1 (+11.1%) 21
Claude Total thread wire 13.7 KiB 13.6 KiB −147 B (−1.0%) 15.1 KiB
Claude Thread snapshot wire 7.0 KiB 7.0 KiB +7 B (+0.1%) 7.3 KiB
Claude Live turn WebSocket wire 6.7 KiB 6.6 KiB −154 B (−2.2%) 7.8 KiB
Claude Live turn WebSocket decoded 59.3 KiB 57.9 KiB −1.4 KiB (−2.4%) 66.4 KiB
Claude Live turn messages 10 10 0 (0.0%) 21

Baseline: c75299e · PR result: d4c37f6 · Source CI: success

Scenario and decoded snapshot size

10 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.

  • Codex decoded thread snapshot: 113.8 KiB
  • Claude decoded thread snapshot: 114.5 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeapp

macroscopeapp Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at d4c37f6

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.

@t3dotgg
t3dotgg merged commit 7839140 into main Sep 4, 2026
24 checks passed
@t3dotgg
t3dotgg deleted the t3code/fix-mobile-draft-storage-guards branch September 4, 2026 16:20
github-actions Bot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 4, 2026
## 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant