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 contained mobile bug fix that prevents premature subscriptions to a thread before its queued creation reaches the server, avoiding the 404/deleted state that caused new threads to remain on “Starting.” Existing server-backed thread behavior and product defaults remain unchanged. You can add or adjust custom eligibility rules. Learn more. |
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (2)
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 6 remain after this review. 📝 WalkthroughWalkthroughMobile thread detail and composer readers now use the nullable selected thread detail reference. If the reference is null, detail readers use fallback atoms. Otherwise, they read state through the scoped reference. ChangesMobile thread reference handling
Priority: ➖ Normal Estimated code review effort: 3 (Moderate) | ~20 minutes Change: Bug fix · Severity of issue fixed: Medium Suggested reviewers: Merge Risk: ⚪ Minimal · up to Pending mobile thread creation avoids premature detail requests, and the reviewed change appears ready to merge. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to The change delays thread-detail loading until the existing creation gate permits it and preserves environment/thread identity. No introduced security concern was established, but creation recovery and authorization coverage is incomplete. Retained concerns Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
Resilience and Maintainability Implications
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Full details: Out of Scope Changes checkExplanation The incremental changes add a separate composer-error feature that is not needed to prevent the premature detail request in [
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
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) |
Problem
When you start a new task from the mobile app with a photo attached, the thread stays on "Starting…" even though the agent runs normally on the server. 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. A photo makes this likely because the outbox checks the uploaded attachment with one more request before it sends
thread.create.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.4ee6bfd50e. The PR commit is the same change rebased ontoa1d9d72aef. Of the commits in between, only fix(mobile): a message that fails to send now says why in the thread #15807 touches the changed files: it adds a send-failure notice toThreadDetailScreenanduse-thread-composer-state.ts. It adds no thread-detail reads, and its only conflict with this PR was an adjacent line. No commit in between touchespackages/client-runtime/src/state/threads.ts.tsc --noEmitpassed inapps/mobile. Lint on the 7 changed files reported 0 errors and the same 54 existing warnings as main. Formatting passed.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: Android (same code), a physical device, and a remote connection without the added delay.
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.