From 13dc3d8042c08a3cb5e6c9ac1f9dab288cc47de4 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Thu, 12 Nov 2026 00:58:56 +0000 Subject: [PATCH 1/2] test(server): thread list status filter skips threads holding awaited background work Failing test for #34668: t3_thread_list filters on activityRunStatus ?? status, so a thread whose turn ended while it waits on an awaited background command is missing from a live-status filter, though the sidebar shows it as Waiting. AI-assisted; verified with the tests and tools listed above. Co-Authored-By: Claude Opus 5.5 --- .../src/mcp/OrchestratorMcpService.test.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/apps/server/src/mcp/OrchestratorMcpService.test.ts b/apps/server/src/mcp/OrchestratorMcpService.test.ts index e0bb1aa96e2f3..89145cc949586 100644 --- a/apps/server/src/mcp/OrchestratorMcpService.test.ts +++ b/apps/server/src/mcp/OrchestratorMcpService.test.ts @@ -3886,6 +3886,11 @@ describe("OrchestratorMcpService thread list", () => { shell("thread:a-snoozed", "project:a", true), shell("thread:a-awake", "project:a", false), shell("thread:b-snoozed", "project:b", true), + { + ...shell("thread:a-awaiting-command", "project:a", false), + activityRunStatus: null, + pendingBackgroundTasks: [{ taskId: "sleep-600", kind: "command" as const, awaited: true }], + }, ]; const service = layerOrchestratorService.pipe( Layer.provide( @@ -3932,6 +3937,20 @@ describe("OrchestratorMcpService thread list", () => { }).pipe(Effect.provide(service)), ); + it.effect("counts a thread holding awaited background work as live in the status filter", () => + Effect.gen(function* () { + const mcp = yield* OrchestratorMcpService.OrchestratorMcpService; + const listed = yield* mcp.listThreads(client, { + projectId: ProjectId.make("project:a"), + statuses: ["preparing", "queued", "starting", "running", "waiting"], + }); + assert.deepStrictEqual( + listed.threads.map((thread) => thread.threadId), + ["thread:a-awaiting-command"], + ); + }).pipe(Effect.provide(service)), + ); + it.effect("points a caller outside any thread at allProjects when it omits projectId", () => Effect.gen(function* () { const mcp = yield* OrchestratorMcpService.OrchestratorMcpService; From 05087f3638171c3671c15c70bac2081d65d249f5 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Thu, 12 Nov 2026 01:06:47 +0000 Subject: [PATCH 2/2] fix(server): t3_thread_list counts threads waiting on background work as live A thread whose turn ended while it holds awaited background work listed as completed and dropped out of live-status filters, though auto-settle and the sidebar treat it as waiting. The list item and its statuses filter now share one status that reads such a thread as waiting. Fixes pingdotgg/t3code#34668 AI-assisted; verified with the tests and tools listed above. Co-Authored-By: Claude Opus 5.5 --- .../server/src/mcp/OrchestratorMcpService.test.ts | 4 +++- apps/server/src/mcp/OrchestratorMcpService.ts | 15 +++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/apps/server/src/mcp/OrchestratorMcpService.test.ts b/apps/server/src/mcp/OrchestratorMcpService.test.ts index 89145cc949586..862263b3a4c6f 100644 --- a/apps/server/src/mcp/OrchestratorMcpService.test.ts +++ b/apps/server/src/mcp/OrchestratorMcpService.test.ts @@ -3889,7 +3889,9 @@ describe("OrchestratorMcpService thread list", () => { { ...shell("thread:a-awaiting-command", "project:a", false), activityRunStatus: null, - pendingBackgroundTasks: [{ taskId: "sleep-600", kind: "command" as const, awaited: true }], + pendingBackgroundTasks: [ + { taskId: "sleep-600", kind: "command" as const, awaited: true as const }, + ], }, ]; const service = layerOrchestratorService.pipe( diff --git a/apps/server/src/mcp/OrchestratorMcpService.ts b/apps/server/src/mcp/OrchestratorMcpService.ts index 960253adc3982..e03e0209f6812 100644 --- a/apps/server/src/mcp/OrchestratorMcpService.ts +++ b/apps/server/src/mcp/OrchestratorMcpService.ts @@ -68,6 +68,7 @@ import { SECRET_REQUEST_UNSAVED_NOTE, } from "@t3tools/contracts"; import { getProviderOptionCurrentValue, withLegacyUltracodeEffort } from "@t3tools/shared/model"; +import { backgroundWorkHoldsCompletion } from "@t3tools/shared/orchestrationV2PendingBackgroundWork"; import { runRanAfter } from "@t3tools/shared/orchestrationV2ThreadError"; import { formatThreadLink } from "@t3tools/shared/threadLinks"; import { @@ -814,6 +815,14 @@ function threadPullRequests( })); } +// Awaited background work keeps a thread live after its turn ends, as auto-settle and the sidebar treat it. +function listedThreadStatus(shell: OrchestrationV2ThreadShell) { + if (shell.activityRunStatus != null) return shell.activityRunStatus; + return backgroundWorkHoldsCompletion(shell.pendingBackgroundTasks ?? []) + ? "waiting" + : shell.status; +} + function listItemFromShell( scope: McpInvocationScope, shell: OrchestrationV2ThreadShell, @@ -826,7 +835,7 @@ function listItemFromShell( link: threadLinkFor(scope, shell.id, shell.title), createdBy: shell.createdBy, creationSource: shell.creationSource, - status: shell.activityRunStatus ?? shell.status, + status: listedThreadStatus(shell), latestRunId: shell.latestRunId, providerInstanceId: shell.modelSelection.instanceId, model: shell.modelSelection.model, @@ -2978,9 +2987,7 @@ const make = Effect.gen(function* () { : new Set(input.statuses); const titleContains = input.titleContains?.toLocaleLowerCase(); const filtered = projectThreads - .filter( - (thread) => statuses === null || statuses.has(thread.activityRunStatus ?? thread.status), - ) + .filter((thread) => statuses === null || statuses.has(listedThreadStatus(thread))) .filter( (thread) => input.settled === undefined || threadSettlement(thread).settled === input.settled,