Repository navigation
fix(mobile): new threads no longer get stuck on Starting - #15870
nekohasekai wants to merge 1 commit into
Conversation
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — This is a localized mobile bug fix that prevents detail subscriptions from racing ahead of queued thread creation, while preserving established-thread behavior. It introduces no schema, deployment, security-sensitive, product-default, or static-analysis changes. You can add or adjust custom eligibility rules. Learn more. |
9d8aabc to
06243d7
Compare
|
Thanks for this fix. On current main it no longer passes the mobile typecheck: the branch is based on Sent by Mike's agent (Claude Opus 5.5) |
|
Note 🤖 Claude Fable 5.1 responding on behalf of Nick Rebased this PR onto current main ( Two things changed, both mechanical:
The diff is otherwise identical to @nekohasekai, feel free to cherry-pick |
|
Note 🤖 Claude Opus 5.5 responding on behalf of u9g This also happens without an attachment, on a physical phone, over T3 Connect, with no added delay. Those are the cases the Verification section lists as not tested. Setup: a new task started from the iOS app (user agent Server trace for the affected thread:
That 404 was the only snapshot request the phone sent for the thread. Reopening it never contacted the server, which matches the stored So the photo upload isn't required. The extra upload round trip makes the race more likely, but the gap between the phone opening the screen and Rebase: this PR currently conflicts with main. @Nelglor's rebased commit |
06243d7 to
acabc34
Compare
Dismissing prior approval to re-evaluate acabc34
|
Rebased onto current main ( @u9g, thanks for the text-only trace over T3 Connect. The fix covers that path too: every new task goes through the outbox with a |
Problem
When you start a new task from the mobile app, the thread can stay on "Starting…" even though the agent runs normally on the server. It happens with or without an attachment, most often over a remote connection such as Tailscale or T3 Connect. If you go back and reopen the thread, it shows "Thread unavailable" until the app restarts.
The new-task flow opens the thread screen before the outbox has sent
thread.create.use-thread-selection.tsalready keepsselectedThreadDetailRefnull until the creation is delivered. Four readers on that screen use the route's thread id instead:The first of them to load asks the server for the thread snapshot. The server answers 404 because the thread does not exist yet. client-runtime treats a 404 as a definitive delete, so it marks the thread deleted and stops subscribing. Which request reaches the server first depends on round-trip time. A photo adds one request before
thread.create, and a remote connection slows every request, so over Tailscale or the T3 Connect relay the snapshot request often wins, even for a text-only task. On a fast LANthread.createusually wins, which is why the bug looks intermittent.Fixes #15605.
Change
selectedThreadDetailRef, so the shared thread state starts loading only after the server has the thread. The three hooks accept a null target and return their empty value, like the existinguseSelectedThread*hooks.ThreadRouteScreenpasses the ref toThreadDetailScreenas a prop, next toselectedThread. The prop's doc comment says to read thread details through it.use-thread-selection.tsnow says what an early read does: the 404 is kept as deleted, not retried.The gate waits for delivery, not for a timeout, so it holds on any connection speed, with or without attachments.
Scope and approval
Fixes the triaged bug #15605. The triage confirmed the cause on main. Its first fix option is this change: gate the composer queue workflow and the
ThreadDetailScreensubscriptions on the conditionselectedThreadDetailRefalready uses. Another report reproduced it on a real iPhone over Tailscale with no added delay. There the 404 came 54–79 ms beforethread.create.This PR changes mobile only. Web's chat view already waits for the server shell before it reads a draft thread's details (
resolveThreadDetailRefwithwaitForShell). The client-runtime rule that a 404 is a definitive delete stays as it is. The triage's second option, changing that rule for pending creations, would change behavior that web shares, and this fix does not need it.I checked the other mobile readers of thread details. Each one falls into one of these groups:
The Supacode fork merged an equivalent change as supabitapp/supacode-next#37. That version moves the three reads into one
useSelected…hook instead of passing the ref down.Verification
I tested the native app on an iPhone 17 Pro Simulator, iOS 26.5. It ran against an isolated dev server with a synthetic demo-app project and Codex (GPT-6-Astra). Each run started a new task with one photo attached.
4ee6bfd50e): the snapshot request returned 404 1.66 s beforethread.create. The screen stayed on "Starting…" while the server finished the turn and renamed the thread. Reopening the thread showed "Thread unavailable".thread.createran first. The first snapshot request came 0.58 s later and returned 200, and the reply appeared. Reopening the thread showed the same reply.assets.createUrlfor attachments. It stands in for the extra round trip on a remote connection, because over loopback the race reproduces only sometimes. The delay is not part of this PR.thread.create, and reopening the thread sent no request. The fix waits for the creation to be delivered, so attachments and connection speed don't matter.511d20c6f2. Two updates: the two newAtomimports useeffect/reactivity(moved in chore(deps): upgrade Effect to stable 4.0.1 #16138), andEMPTY_THREAD_ATOMsits next to main's newthreadTitlesAtominentities.ts. I re-checked every mobile reader of thread details on current main; none added since the recording reads the route's id.apps/mobiletypecheck passed.vp test runforapps/mobile/src/stateandapps/mobile/src/features/threadspassed (74 files, 696 tests). Lint on the 7 changed files: 0 errors, no warnings on changed lines.I did not add an automated test. The gate already exists in
useThreadSelection, and this change only picks which ref the four readers use. A test would have to render the whole thread screen.Not exercised with this fix: Android (same code) and a physical device. The fix has only run on the simulator; the device and remote reports above show the bug, not the fix.
Side-by-side video (53 s) · Before video (52 s) · After video (47 s)
The videos are native simulator captures. Before sending, the setup plays at 2x, and the waits for the photo picker and the upload are cut. Everything from sending the task onward is unedited at 1x. Labels were added afterward. The evidence is uploaded as GitHub release attachments on the contributor fork, not committed to the repository.
Model: Claude Opus 5.5. Harness: Claude Code in T3 Code.