Skip to content

fix(ios): surface new global/ensemble chat create failures instead of hanging#5

Merged
boggspa merged 1 commit into
masterfrom
fix/ios-new-chat-failure-feedback
Jun 18, 2026
Merged

fix(ios): surface new global/ensemble chat create failures instead of hanging#5
boggspa merged 1 commit into
masterfrom
fix/ios-new-chat-failure-feedback

Conversation

@boggspa

@boggspa boggspa commented Jun 18, 2026

Copy link
Copy Markdown
Owner

Symptom

On iOS you can't start a new global chat or a new ensemble chat — tapping the option spins on "Creating…" forever (a regular workspace chat works).

Root cause

The create→navigate path is the same for all three variants. The difference: the Mac legitimately declines some creates —

  • ensemble: ensembleModeEnabled === false (src/main/index.ts:16493),
  • global: the global scope isn't shared when the workspace allowlist is empty (src/main/RemoteWorkspaceAllowlist.ts:359),

…but RemoteSessionModel.send only fires the create callback when the ack is accepted (RemoteSessionModel.swift:2615). A declined create therefore never reached NewChatBootstrapView, which kept spinning on "Creating…" with no error and no retry. That silent hang is exactly what "can't start" looks like.

Fix (iOS)

  • createEmptyThread now propagates failure through send's onAckonCreated(nil) on a declined/failed ack (the generic send is untouched, so no blast radius on other actions).
  • NewChatBootstrapView gains a failure state: it shows the Mac's actual reason (model.lastActionMessage, e.g. "Ensemble mode is disabled on your Mac.") + a Try Again button, and clears the latched requestedKey so retry re-requests.

So instead of an infinite spinner you now see why it didn't start and can retry.

Important

This makes the failure visible + actionable; it does not override the Mac-side policy gates. If the reason shown is "ensemble mode disabled" → enable Ensemble mode on the Mac; if it's the global-allowlist message → share at least one workspace to the device. If the revealed reason is something unexpected (e.g. global fails even with workspaces shared), that surfaced message will tell us exactly what to chase next.

Verification

swift build + 69 Kit tests + iOS app build (iPhone 17 sim) — all green.

🤖 Generated with Claude Code

Starting a new GLOBAL or ENSEMBLE chat could spin on "Creating…" forever.
The Mac legitimately declines some creates (ensemble mode disabled; global
not shared while the workspace allowlist is empty), but `send` only fires
the create callback when the action is ACCEPTED — so a denial never reached
NewChatBootstrapView, which never stopped spinning and never showed why.

- createEmptyThread now propagates failure via send's onAck (onCreated(nil)
  on a denied/failed ack); the generic send is unchanged.
- NewChatBootstrapView shows a failure state with the Mac's reason
  (model.lastActionMessage) + a Try Again button, and clears the latched
  requestedKey so retry actually re-requests.

Verified: swift build + 69 Kit tests + iOS app build (iPhone 17 sim).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@boggspa boggspa merged commit c9b484d into master Jun 18, 2026
15 of 16 checks passed
boggspa added a commit that referenced this pull request Jun 18, 2026
Build 21 ships the merged iOS work: turn-based guest participation (PR #3,
Mac-side bridge), active-chat/sidebar/Settings-sheet state preservation
across settings changes (PR #4), and surfaced new global/ensemble chat
create failures with retry instead of an infinite spinner (PR #5).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
boggspa added a commit that referenced this pull request Jun 23, 2026
Roadmap #5, compounds with #1. Wrap MarkdownMessage in React.memo with a
comparator that re-renders only when the rendered output can change: the
markdown content, the chat scope (appChatId), or the identity registry
<AgentMention> reads (chat.providerMetadata.agentIdentities). The `chat` prop
is the whole currentChat — a new object on every coalesced flush — so a
reference compare would defeat the memo; comparing the identity-relevant slice
instead means a per-frame transcript re-render only re-renders the ONE message
that streamed, not every visible bubble. Also useMemo the block split so a
re-render not caused by a content change doesn't re-run the O(n) string scan.

renderToStaticMarkup output unchanged; markdown + transcript tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
boggspa added a commit that referenced this pull request Jun 25, 2026
…e-inject, actionable denial, Settings signpost (PR5)

Make agents aware the image tools (image_edit/svg_rasterize/image_generate)
exist, without heavy prompt injection (the user's point #5). Tool descriptions
were already rich (Layer 1); this adds the prompt + UI layers:

- PromptComposition: the write-capable cloud preamble now names the image tools
  (one line). Bumped the preamble version v2->v3 so existing resumable sessions
  re-learn it once. Added promptNeedsImageToolsHint + a resumed-session
  re-inject: when the full preamble is suppressed (resumed gemini/claude/codex)
  and THIS turn is image-related, re-inject just the image-tools note. Skipped on
  global/plan runs (which never get the tools).
- index.ts: image tools are gated as File changes, so a read-only/plan preset
  denied them with a generic 'File changes denied by TaskWraith.' — now an
  actionable message naming the write-capable-preset (and key) requirement.
- SettingsPanel: the MCP bridge card now states it enables the image tools for
  Grok & Cursor (Claude/Codex/Gemini get them natively; image_generate also
  needs a key).

35 PromptComposition tests (incl. 5 new); typecheck node+web clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant