Repository navigation
[Bug]: Mobile draft-save failure shows Retry and blocks Send after successful attachment upload #21097
Description
Before submitting
- I searched existing issues and did not find a duplicate of this failure path.
- I included enough detail to reproduce or investigate the problem.
Area
apps/mobile
Steps to reproduce
Deterministic reproduction on a disposable Android development client:
- Run upstream commit
16918b57d6754d6f145e9b6367c825aa31a740dawith an isolated backend. Pair the native client, open an existing thread, and enter some composer text so its draft directory exists. - Make only the app's
files/composer-draftsdirectory unwritable, changing its mode from700to500. Keep the outbox directory writable. This induces a real draft-write failure without a transport failure. For the debug package, the test usedadb -s <serial> shell run-as com.t3tools.t3code.dev chmod 500 files/composer-drafts. - Attach a small PNG through the native Files picker. The server accepts the upload with HTTP 204; saving
composer-drafts/drafts.jsonthen fails. - Observe Retry on the attachment thumbnail and disabled Send. Retry repeats the failure while draft storage remains unwritable. Restore the original directory mode and contents after testing.
Expected behavior
A draft-storage failure should not prevent the user from queuing the attachment through the existing durable outbox when the local file is available and outbox storage is healthy. Sending must still wait for a durable outbox entry and valid uploaded attachment reference; a failed draft save should not be represented as successful.
Actual behavior
The background worker successfully uploads the image, then its flushComposerDrafts() callback rejects with ComposerDraftPersistenceError. The preparation cleanup releases the new pending upload. The queue marks the attachment failed, and the composer blocks Send as though the file transfer itself failed.
The original report was an Android installed release APK: pasting a screenshot from Photos reached 100%, then showed Retry on the thumbnail and grayed out Send. Rapidly pressing Retry and Send sometimes delivered the image. The original phone's exact exception is unknown; the controlled reproduction above establishes this specific failure path rather than proving every similar symptom has the same cause.
Impact
Major degradation or frequent failure: the affected message cannot be sent normally while draft persistence is failing, despite a working server connection and independently writable outbox.
Version or commit
Reproduced on upstream 16918b57d6754d6f145e9b6367c825aa31a740da. Fix comparison: 55b6036e96a67baa928f0fdd0da83bbc08604f18 in #21084.
Environment
Android 17 emulator; upstream debug package com.t3tools.t3code.dev; React Native 0.88.0 / Expo 58.0.14; local isolated backend and Metro. A real 3,406-byte PNG was selected through Files. A controlled provider peer avoided real inference. iOS, a release build with the fix, and the original Photos clipboard source were not verified in this controlled test.
Logs or stack traces
Observed sequence: upload HTTP 204, followed by ComposerDraftPersistenceError for composer-drafts/drafts.json. The native accessibility snapshot reports the action “Retry or remove the failed attachment” as disabled. With #21084, Queue is enabled, a direct tap persists the outbox and re-uploads, and the delivered server image matches the source bytes exactly.
Screenshots, recordings, or supporting files
18-second baseline recording:
baseline-before.mp4
The baseline was recaptured after restoring unmodified source; the delivered image above the composer is prior test context, not the currently failed draft. Full before/after recordings, persistence checks, and 224 focused test results are in #21084.
Workaround
No reliable user-facing workaround established. Racing Retry and Send sometimes worked in the original report. Restoring draft-directory permissions removes the deliberately injected test fault.
Related work
Proposed fix: #21084. This is distinct from #1304 (old web/desktop premature draft verification) and #11427 (iOS HEIC import/preview freeze).
Filed with GPT-6 Astra (xhigh), with search and native verification by GPT-6.1 Sol (xhigh), using the Codex harness in Harbordex.

Activity