Skip to content

Commit 237bb20

Browse files
T3 Code Testclaude
andcommitted
fix(mobile): drop a stale recovery error once a rejected new task is restored
If restoring a rejected new task failed once, the retry's success recorded the failure card but left that earlier error above it. Successful creation recovery now withdraws any error tagged with its message. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
1 parent 23a1eea commit 237bb20

2 files changed

Lines changed: 34 additions & 0 deletions

File tree

‎apps/mobile/src/state/use-thread-outbox-drain.test.ts‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,37 @@ describe("thread outbox recovery rollback", () => {
729729
).toEqual({ kind: "failed", message, reason: "rejected by server" });
730730
});
731731

732+
it("drops an earlier recovery error once a rejected new task is restored", async () => {
733+
const message: QueuedThreadMessage = {
734+
...queuedMessage({ messageId: "message-creation-retried", text: "new task text" }),
735+
modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.6-sol" },
736+
creation: {
737+
projectId: ProjectId.make("project-1"),
738+
workspaceMode: "local",
739+
branch: null,
740+
worktreePath: null,
741+
},
742+
};
743+
await harness.manager.enqueue(message);
744+
harness.draftFile.setWriteError(new Error("disk full"));
745+
await expect(restoreRejectedQueuedMessage(message, "rejected by server")).resolves.toBe(
746+
"retry",
747+
);
748+
const threadKey = `${message.environmentId}:${message.threadId}`;
749+
expect(appAtomRegistry.get(threadComposerErrorsAtom)[threadKey]?.messageId).toBe(
750+
message.messageId,
751+
);
752+
753+
harness.draftFile.setWriteError(null);
754+
await expect(restoreRejectedQueuedMessage(message, "rejected by server")).resolves.toBe(
755+
"restored",
756+
);
757+
758+
// The failure card carries the reason now; nothing stale sits above it.
759+
expect(appAtomRegistry.get(threadComposerErrorsAtom)).toEqual({});
760+
expect(appAtomRegistry.get(pendingThreadCreationOutcomesAtom)[threadKey]?.kind).toBe("failed");
761+
});
762+
732763
it("keeps a failed outcome until its thread screen consumes it", async () => {
733764
const message: QueuedThreadMessage = {
734765
...queuedMessage({ messageId: "message-creation-kept", text: "new task text" }),

‎apps/mobile/src/state/use-thread-outbox-drain.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,9 @@ export async function restoreRejectedQueuedMessage(
556556
// must never be rolled back.
557557
rollback = null;
558558
if (queuedMessage.creation) {
559+
// The failure card shows the reason, so an error left by an earlier
560+
// failed attempt at this recovery no longer applies.
561+
withdrawError();
559562
// The thread screen for this creation is likely open; it reads the
560563
// outcome to offer reopening the restored draft, and shows the reason.
561564
recordPendingThreadCreationOutcome({

0 commit comments

Comments
 (0)