fix(mobile): opening a new thread before it is created no longer marks it unavailable#25632
Problem
On mobile, if you tap a new thread's row in the thread list while its first message is still queued (the sidebar in split view, or the Home list), the thread stays on "Starting…" and never shows the run. Reopening it within 5 minutes shows "Thread unavailable: This thread was deleted or is no longer available." The server created the thread and ran it, and other clients show it normally.
Fixes #25586. #24344 reports the same symptom; #25477 fixed it for ThreadFeed's readers, and this is a second path that #25477 doesn't cover.
Change
Tapping a thread row calls startOpeningEnvironmentThread, which preloads the thread's detail before navigation commits (#24750). It mounted the thread state by id without checking the outbox, so a tap before thread.create was acknowledged requested the snapshot too early. The server answered 404, and client-runtime cached the thread as deleted and stopped requesting it (missing → setDeleted in packages/client-runtime/src/state/threads.ts). That is the same state the thread screen reads once the creation lands.
The preload now applies selectThreadCreationDetail, the check useThreadSelection already uses, and does nothing while the thread's creation is queued or has failed. Once the creation is delivered, the thread screen loads the detail as it already does. Threads with no queued creation preload exactly as before.
This is mobile only. Web and desktop have no outbox: the first send waits for the server, and detail reads wait until the thread is listed (waitForShell in ChatView.tsx).
Scope and approval
This is a small, focused fix for an obvious bug: one 11-line guard in one function, plus a test. It applies an existing rule from pending-thread-creation.ts ("Every detail reader must use this ref, not the route id"), which #25477 introduced when it fixed the same failure for ThreadFeed. The tap preload was the remaining reader that skipped it. Issue: #25586. Related: #24344.
Verification
Focused test. apps/mobile/src/state/threads.test.ts is new and uses the real atom registry, outbox manager and creation outcomes. It records whether the preload loads the thread's state.
- With
main'sthreads.ts, 2 of 3 fail: the preload loads a thread whose creation is still queued, and one whose creation failed. - With this change, 3 of 3 pass, including the delivered case and threads with no queued creation.
vp test run src/state src/featuresinapps/mobilepasses: 1050 tests in 149 files.vp run check:changedpasses: format, lint, knip, mobile typecheck and tests.
Live, in iOS simulators.
- Setup: Xcode 27.1; iPhone Duo on iOS 27.1, unfolded into split view; iPhone 17 Pro on iOS 26.3. The dev client ran against a local server on
main210b2e7c6a, with made-up projects and a provider that always fails, so no requests left the machine. - To make the window repeatable, the local server waited 10 s at the start of
thread.create. That wait is not part of this PR. In the incident trace quoted in #25586 (iOS build 164 over T3 Connect, which may predate #25477), the phone's 404 came 3.6 s beforethread.createwas dispatched.
| Row tapped while the creation was queued | Before (main) | After (this PR) |
|---|---|---|
| Duo, sidebar | 4/4 stuck on "Starting…", 4/4 "Thread unavailable" on reopen | 6/6 load the run, 6/6 reopen normally |
| iPhone, Home list | 4/4 stuck, 3/4 "Thread unavailable" on reopen (the fourth was reopened after the 5-minute cache expired and loaded) | 4/4 load the run, 4/4 reopen normally |
Server GET …/threads/<id>/bounded | one 404 per tapped run (8 in total), and no further request while the 5-minute cache held the thread | no 404s for any after-fix or edge-case thread (22 requests, all 200) |
Without tapping the row, main opened normally in 4 of 4 runs. Five after-fix attempts aren't counted: two on the Duo where the scripted tap hit the detail pane instead of the row, and three on the iPhone where the tap landed after the commit and opened a different thread. None of them produced a 404.
Edge cases on this branch:
- Creation lands inside the old 3 s preload hold (row tapped about 8.5 s after Send): 2 of 2 open normally.
- Server offline while sending: tapping the queued row shows the pending state. After reconnecting, the thread is delivered and loads. One run on each device.
- Creation fails (project removed while the creation was queued; Duo, one run per build): both builds show the same "Could not start task" card. The row turns into a draft row that opens the project picker, so neither build requests the thread. This run doesn't reach the guard's failed-creation branch; the focused test covers that.
- Already-created threads still preload on tap. Time from tap to request, measured with the host under the same load: this branch 305–1077 ms,
main308–1159 ms.
All four GIFs play at 1.5× speed. The after clips cut out the automated scroll that finds the row before reopening it.
Stills at the moment of reopening
Not checked:
- Android: it shares this code, but I didn't run it.
- A physical device, or a real T3 Connect connection.
- A cold start that restores a queued thread's screen before the outbox has loaded from disk. That path is unchanged and outside this fix.
Model: Claude Opus 5.5 via Claude Code, in T3 Code; reviewed by GPT-6.1-Sol (Codex) and Claude Opus 5.5.
Checks
Added 1 commit
- fix(mobile): opening a new thread before it is created no longer marks it unavailable08afa33
Approvability
Verdict: Approved at 08afa33
Macroscope's review found this PR approvable — This is a narrowly scoped mobile bug fix that prevents a premature thread-detail request while creation is queued or failed, preserving existing behavior for delivered threads. The accompanying tests cover the affected states and add no production capability or configuration changes.
You can add or adjust custom eligibility rules. Learn more.
Important
Review skipped
Review was skipped as selected files did not have any reviewable changes.
External image Run configuration
- Source mode: Auto
- Repository: pingdotgg/t3code
- Branch: main
- File: .coderabbit.config.ts
- Commit: c175e57
- Layers: Repository TS config
- Review profile: CHILL
- Plan: Advanced
- Run ID:
58fcd883-5b1a-4829-a0fd-24117142c56c
External image Commits
Reviewing files that changed from the base of the PR and between 08afa3337b70d07b519e108a8f0a06a8146b8b8f and 0a511f74d603ccbe587def290412b8999fa482e9.
You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.
Use the checkbox below for a quick retry:
- 🔍 Trigger review
No actionable comments were generated in the recent review.
External image Recent review info
-
External image Run configuration
- Source mode: Auto
- Repository: pingdotgg/t3code
- Branch: main
- File: .coderabbit.config.ts
- Commit: fe507c5
- Layers: Repository TS config
- Review profile: CHILL
- Plan: Advanced
- Run ID:
09d0f53a-541b-402a-8c7d-c85f6c00e7f0
External image Commits
Reviewing files that changed from the base of the PR and between fe507c5a1e064c82e7b49a002e5f9520cfaeaaac and 08afa3337b70d07b519e108a8f0a06a8146b8b8f.
External image Files selected for processing: 2
apps/mobile/src/state/threads.test.tsapps/mobile/src/state/threads.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 7 remain after this review.
Walkthrough
Thread opening now checks pending creation details before mounting thread state. Tests cover queued and failed creation outcomes, plus threads without pending creation data.
Changes: Mobile thread preloading
Layer / File(s) Summary Check creation readiness before mounting thread state
apps/mobile/src/state/threads.ts,apps/mobile/src/state/threads.test.tsstartOpeningEnvironmentThreadskips mounting when the selected creation detail reference is null. Tests cover queued creation becoming delivered, failed creation, and threads without creation data.Priority: External image Low
Estimated code review effort: 2 (Simple) | ~10 minutes
Change: Bug fix · Severity of issue fixed: Low
Possibly related PRs
- pingdotgg/t3code#24345: Defers a mobile workflow detail reader until queued thread creation is delivered; this PR applies a similar guard to the preload path.
- pingdotgg/t3code#25477: Adds the creation-detail selector that this PR uses in
startOpeningEnvironmentThread. - pingdotgg/t3code#24750: Introduces
startOpeningEnvironmentThread, which this PR updates to skip mounting when creation details are unavailable.
Suggested labels:
bugfix,size:SSuggested reviewers:
t3dotgg,macroscopeapp
Merge Risk: External image Minimal · previous review 08afa
Queued threads can open after creation without a premature unavailable state. No actionable merge-blocking risk remains after normal checks.
Pre-merge checks: Passed 4
Check name Status Explanation Title check Passed The title uses a clear conventional-commit format and accurately describes the primary mobile fix: preventing a newly queued thread from being marked unavailable during opening. Description check Passed The description fully covers the required Problem, Change, Scope and approval, and Verification sections. It includes reproduction context, linked issues, implementation details, focused and broader t… Linked Issues check Passed Directly linked issue #25586requires that opening a thread-list row while its creation is queued lets the thread open after the server creates it. At head, startOpeningEnvironmentThread in apps/mobil…Out of Scope Changes check Passed The whole-PR diff against merge base 210b2e7c6a touches two files. apps/mobile/src/state/threads.ts gains an 11-line guard with imports for scopedThreadKey, selectThreadCreationDetail, pendingThreadCr… Full details: Description check
Explanation
The description fully covers the required Problem, Change, Scope and approval, and Verification sections. It includes reproduction context, linked issues, implementation details, focused and broader test results, simulator evidence, limitations, and agent attribution.
Full details: Linked Issues check
Explanation
Directly linked issue
#25586requires that opening a thread-list row while its creation is queued lets the thread open after the server creates it. At head, startOpeningEnvironmentThread in apps/mobile/src/state/threads.ts calls selectThreadCreationDetail before mounting environmentThreads.stateAtom. When the detailRef is null (creation queued or failed), the function returns without mounting. This stops the early snapshot request that the server answers with 404 and that client-runtime caches as deleted. Threads with no queued creation, or with a delivered creation, still mount and preload as before. The new apps/mobile/src/state/threads.test.ts covers three cases: queued creation does not load until delivered, failed creation does not load, and threads without a pending creation load. The tests use the real outbox and outcome atoms. The author reports 2 of 3 failing on main and 3 of 3 passing with this change, and reports simulator runs with no 404s. Residual uncertainty: I did not re-inspect useThreadSelection in this review. The post-delivery load relies on that existing guarded reader, as the issue and PR description state. The author's simulator evidence supports this, but this review did not verify it in code. Android, physical devices, and cold-start restoration are not covered, and the PR description states this. Related#24344is covered only by the same root cause and is not a separate requirement here.
Full details: Out of Scope Changes check
Explanation
The whole-PR diff against merge base 210b2e7c6a touches two files. apps/mobile/src/state/threads.ts gains an 11-line guard with imports for scopedThreadKey, selectThreadCreationDetail, pendingThreadCreationOutcomesAtom, and threadOutboxManager. apps/mobile/src/state/threads.test.ts is a new test file for the same function. The guard reuses the existing selectThreadCreationDetail rule and its comment, which the issue references. The test exercises that guard directly. No unrelated code, configuration, or dependency change appears in the reviewed diff. The PR description's screenshots and GIFs are evidence material and do not change code. No change in the reviewed diff lacks a connection to
#25586.
Finishing Touches
🧪 Generate unit tests (beta)
- Create a new PR
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Comment @coderabbitai help to get the list of available commands.
These are real bugs I experienced live from my device. Then I deep dived alongside agents for this fix. Using the new iPhone DUO as well.
Added 1 commit
- Merge branch 'main' into fix/mobile-new-thread-unavailable0a511f7