# fix\(mobile\): keep pending tasks queued when a send fails in flight · PR \#10245 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/10245)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

State: merged

Source repository: pingdotgg/t3code

Source branch: t3code/fix-offline-pending-tasks

Target branch: main

Head commit: 9d738dab392e70bad8083163f8214de4c14f2980

[Discussion and reviews](https://git.cafe/pingdotgg/t3code/pull/10245)

[Commits](https://git.cafe/pingdotgg/t3code/pull/10245/commits?format=markdown)

[Files changed](https://git.cafe/pingdotgg/t3code/pull/10245/diffs?format=markdown)

## Description

---

## Problem

A pending task queued on the phone while offline was gone from the **Pending** section once the phone reconnected, and its text showed up as the project's new-task draft instead.

The outbox drain sends a queued creation as soon as the environment reconnects, which is exactly when the socket is most likely to drop again. When a `startTurn` fails in flight, Effect fails the request with an `RpcClientError` wrapping the socket reason. For a socket that dies after opening, that reason is `SocketReadError`, whose message is just `"An error occurred during Read"`. `shouldRetryThreadOutboxDelivery` classified failures by message text and only special-cased the `ConnectionTransientError` tag, so this read as a deterministic server rejection. The drain then took the `restore` path, which merges the queued text into the `new-task:<project>` draft and deletes the outbox entry.

The server side confirms it never rejected anything: no non-accepted command receipts, no `bootstrap-thread-delete` cleanup, and every mobile `dispatchCommand` span in the trace succeeded. The failure was client-side transport.

## Fix

Classify by error tag first, and only fall back to the message matcher for untagged errors:

- **Restore (payload is bad):** `OrchestrationDispatchCommandError`, `EnvironmentAuthorizationError` — the two errors the `dispatchCommand` RPC contract can return.
- **Retry (transport):** `ConnectionTransientError`, `RpcClientError` (any socket reason or protocol defect), `EnvironmentRpcUnavailableError`, `EnvironmentNotRegisteredError`.

## Verification

Added a test that constructs real `RpcClientError` instances for all four socket reasons plus a protocol defect and asserts each retries. It also pins that `SocketReadError`/`SocketWriteError` messages are *not* recognised by the text matcher, which documents why tag matching is required. The test fails on `main` and passes here.

```
apps/mobile: vp test run src/state/thread-outbox.test.ts   43 passed
apps/mobile: vpr typecheck                                   ok
```

No screenshots: the change is a classification function with no UI. Follow-up work to surface drafts and pending tasks together in the mobile sidebar is stacked on top of this PR.

Model: Claude Fable 5 · Harness: Claude Code in T3 Code

🤖 Generated with [Claude Code](https://claude.com/claude-code)


<!-- Macroscope's pull request summary starts here -->
<!-- Macroscope will only edit the content between these invisible markers, and the markers themselves will not be visible in the GitHub rendered markdown. -->
<!-- If you delete either of the start / end markers from your PR's description, Macroscope will append its summary at the bottom of the description. -->
> [!NOTE]
> ### Fix `shouldRetryThreadOutboxDelivery` to stop retrying deterministic failures and retry RPC errors
> - Replaces the single `ConnectionTransientError` check in [thread-outbox-model.ts](https://github.com/pingdotgg/t3code/pull/10245/files#diff-70b765fdc85f11eba546273ca7f2459bb1a0ed084acdec2d67bce652f3cd7895) with tag-based classification so pending tasks stay queued on transient failures instead of being dropped
> - `OrchestrationDispatchCommandError` and `EnvironmentAuthorizationError` are now non-retryable; `RpcClientError`, `EnvironmentRpcUnavailableError`, and `EnvironmentNotRegisteredError` join `ConnectionTransientError` as retryable
> - Untagged errors still fall through to the existing message-based transport detection
> - Adds tests in [thread-outbox.test.ts](https://github.com/pingdotgg/t3code/pull/10245/files#diff-72c27bc8fd8aafae9a96593859117a27e1d454f06dd58bc981343e1243768f79) covering typed retryable and non-retryable cases
> - Behavioral Change: errors previously retried only via message matching are now classified by tag; untagged errors with unrecognized messages may stop retrying
>
> <!-- Macroscope's review summary starts here -->
>
> <sup><a href="https://app.macroscope.com">Macroscope</a> summarized 9d738da.</sup>
> <!-- Macroscope's review summary ends here -->
>
<!-- Macroscope's pull request summary ends here -->
