fix(mobile): opening a new thread before it is created no longer marks it unavailable#25632

merged2
main
GitHub fork: fix/mobile-new-thread-unavailable
opened1d agobyWwhoisaldo
View on GitHubTwo-way sync enabledPR last imported 16h ago

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's threads.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/features in apps/mobile passes: 1050 tests in 149 files.
  • vp run check:changed passes: 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 main 210b2e7c6a, 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 before thread.create was dispatched.
Row tapped while the creation was queuedBefore (main)After (this PR)
Duo, sidebar4/4 stuck on "Starting…", 4/4 "Thread unavailable" on reopen6/6 load the run, 6/6 reopen normally
iPhone, Home list4/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>/boundedone 404 per tapped run (8 in total), and no further request while the 5-minute cache held the threadno 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, main 308–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

21 successful · 15 skipped
All checks passed
0a511f7

Added 1 commit

  • fix(mobile): opening a new thread before it is created no longer marks it unavailable08afa33
Synced from GitHub

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.

Synced from GitHub

Review in Change Stack →

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

External image Run configuration
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
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.ts
  • apps/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.ts
startOpeningEnvironmentThread skips 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:S

Suggested 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 nameStatusExplanation
Title checkPassedThe 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 checkPassedThe 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 checkPassedDirectly linked issue #25586 requires 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 checkPassedThe 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 #25586 requires 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 #24344 is 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.

Synced from GitHub

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