A thread waiting on its own background command shows as done unless the agent marks the wait; T3 could mark obvious waits itself
维护者通常 1 天内回复
还没有人认领这个 Issue。
评估
- 难度
- 5/5
- 预计耗时
- 一周以上
- 新手友好度
- 32/100
- Issue 类型
- 功能
- 描述清晰度
- 描述清楚
- 活跃度
- 活跃
- 技术栈
- typescript
调研方向
Read packages/contracts/src/orchestrationV2.ts and packages/shared/src/orchestrationV2PendingBackgroundWork.ts to understand how awaited tasks hold completion, then trace task registration and text generation through ThreadManagementService and apps/server/src/textGeneration/TextGeneration.ts. The issue proposes classifying unlabelled background tasks from their command, description, and nearby agent text; done means inferred waits hold thread completion while unclear tasks and failures preserve current behavior. This spans orchestration, provider adapters, and model calls, so it needs substantial project familiarity.
由索引模型根据 Issue 内容生成。
描述
What happened
An agent thread is still working: the agent started a background command to wait until its validation gates finish, and it will act when that command exits. The composer says "Running 2 commands · Wait for 184 gates (long), Wait for 184 gates to complete". The sidebar shows neither Working nor Waiting, so the thread looks done. T3 knows something is running in the background but ignores it when deciding the thread's status.
Diagnosis
The thread's state, read through the orchestrator MCP: status: "completed", activeRunId: null, pendingRequestCount: 0. The last run ended right after the agent:
- started a background command
until grep -q DONE <evidence>/coord-status.txt; do sleep 60; done; cat …with the description "Wait for 184 gates", - wrote "I'm waiting for the gates, then I decide whether to merge",
- ended its turn without calling
t3_background_wait.
An earlier run did the same with another wait command.
How T3 decides today
- Each pending background task has a provider-neutral shape,
{ kind, command, description, awaited }(packages/contracts/src/orchestrationV2.ts:1025-1038). backgroundWorkHoldsCompletion(packages/shared/src/orchestrationV2PendingBackgroundWork.ts:90-108) holds the thread for acommandormonitoronly whenawaited === true.awaitedis set only when the agent callst3_background_wait, which goes throughThreadManagementService.awaitBackgroundTasksto the provider adapter's optionalawaitBackgroundTasks.
The comment on that field states the assumption behind all this: "Only the agent knows whether a command is a wait or a dev server it left running."
Why that's fragile, independent of provider
- The model must remember one specific call after starting the command. When it forgets (as here), the thread looks done even though the agent will wake up.
- Not every provider can mark a wait. In the source I read, only the Claude adapter implements
awaitBackgroundTasks; other providers getunsupported. Codex also wakes its agent when a background command exits (CodexAdapterV2.ts, background command continuation), but has no way to mark that wait. - The call itself can fail:
- the guidance names
taskIdbut the schema needstaskIds(#31889) - registration has a 15s timeout that busy sessions hit (#33646)
- the guidance names
Where the assumption is too strong: the agent is the only one who decides whether a command is a wait, but it usually says so in plain words, in the command's description ("Wait for 184 gates") and in the text around it ("I'm waiting for the gates" vs "the server is on localhost:3000"). T3 already has that text. It just doesn't read it.
Suggested fix
Keep the decision from #15433 / #24610 as it is: an awaited command holds the thread, and a dev server left running does not. Change only who can set awaited. When a background task starts and the agent hasn't labelled it, T3 asks its existing text-generation model whether this is a wait, and if so marks it awaited itself.
- It reuses an existing service.
apps/server/src/textGeneration/TextGeneration.tsalready runs short model jobs for T3 itself (generateThreadTitle,generateBranchName,generateCommitMessage,generatePrContent,generateReviewGuide,generateTranscriptionPostProcessing). It has drivers for several providers and uses the model the user picked intextGenerationModelSelection. This would be one more operation, e.g.classifyBackgroundTask. No new provider coupling, and nothing reaches a model that the user hasn't already sent titles, diffs and transcripts to. - Input: the command, its description and the agent's text just before and after starting it. Output:
waitornot_wait, usingwaitonly when the agent clearly intends to act on the result. Fixed rules wouldn't work: custom scripts (./scripts/qa.sh,bun run e2e:full) and the agent's own words ("tell me when to stop it") decide the answer, and rules can't read either reliably. - When: as soon as the task starts. A background command returns right away and the turn usually goes on, so the answer is normally there before the turn ends. If it isn't, the completion notification waits a moment. Either way the thread doesn't flash Done, and there's no race with auto-settle.
- Provider-neutral: it runs over the shared
{ command, description }task shape where pending background tasks are recorded or projected. So every provider that reports background commands benefits, including those withoutawaitBackgroundTasks. - A failure keeps today's behaviour. If the call fails, times out or text generation is off, the task stays un-awaited, exactly as now. The classifier can only add correct "Waiting" states, never break existing ones.
- The agent's word wins. Tasks the agent already labelled skip the call. An explicit
t3_background_wait({ taskIds, waiting: false })un-marks an inferred wait, andwaiting: truemarks one the model missed. The user still has Stop. Becauseawaitedis currentlyLiteral(true), keeping an explicit "not a wait" needs that state stored somewhere (e.g. a per-task override next to the inferred value). - Cost: one short call per unlabelled background task, about the size of naming a thread.
- No new kinds, UI or notification paths. The sidebar Waiting status, notifications, auto-settle and the composer strip already read
awaited.
This thread's commands are easy cases: an until … sleep loop described as "Wait for 184 gates", started right after "I'm waiting for the gates". T3 already stores every background task's command and description, so the classifier can be checked against existing threads before merging.
Steps to reproduce
- In any thread whose provider runs background commands (reproduced with Claude), ask: "Run
until [ -f /tmp/t3-done ]; do sleep 10; donein the background, tell me you're waiting for it, and end your turn. Don't call t3_background_wait." - Once the turn ends, the composer shows "Running 1 command".
- The sidebar row shows no Working or Waiting status, and the completion notification fires.
- Run
touch /tmp/t3-done. The agent wakes and keeps working in a thread that already reported itself as done.
Version
0.1.10-nightly.20261112.4564 (the server that ran the thread)
Environment
Server on Linux x64 (cloud). The thread ran on the Claude provider (claude-sonnet-5-5), viewed through the web client. The code paths above are provider-neutral, except awaitBackgroundTasks, which is implemented per adapter.
Evidence
# t3_thread_read (activity), redacted
thread.status=completed activeRunId=null pendingRequestCount=0 completedAt=2026-11-12T10:25:27Z
09:44:10 notification Command "Wait for 184 gates" was stopped
09:44:21 command E=~/<evidence>; sleep 30; until grep -q DONE $E/coord-status.txt 2>/dev/null; do sleep 60; done; cat $E/coord-status.txt
-> started in background (task bpouz203c)
09:44:35 assistant "...gates restarted. They're waiting for the shared lock..." (turn ends, no t3_background_wait)
10:25:22 command until grep -q DONE $E1/coord-status.txt 2>/dev/null; do sleep 60; done; echo 184; cat $E1/coord-status.txt
-> started in background (task b22im3ntx)
10:25:27 assistant "...I merge once the gates are green. When the gate queue reaches..." (turn ends, no t3_background_wait)
Related issues
This builds on earlier discussion rather than reopening it:
- #14872, #15114: commands stopped holding completion because dev servers left as hand-offs pulsed "Waiting" for hours. This proposal keeps that: servers and anything unclear stay un-awaited.
- #15413 and discussion #15433: "any live command holds the thread" was declined, and #24610 (
t3_background_wait) was chosen so the agent declares its waits. This proposal keeps that mechanism and fills the gap when the agent doesn't declare one, on any provider. - #26689: deadlines and lapse reasons for declared waits. That work is complementary and would apply to inferred waits the same way.
- #25585: Done shown at every intermediate stop of multi-stage background jobs. It has the same symptom; this issue adds the root cause and a specific fix, so it's filed separately rather than as a comment there.
- #17625 / #18062: auto-settle while a background command runs. An inferred wait holds auto-settle through the existing rule.
- #31889, #33646: ways the agent-side call fails today.
Fix applied or workaround
None applied. Workaround: tell the agent to call t3_background_wait with the task IDs of its wait commands.
Filed by
claude (claude-opus-5-5) via T3 Code, following the t3 triage playbook
- 主要语言
- TypeScript
- 星标
- 24.8k
- 派生
- 6.4k
- 平均合并
- 4 小时 16 分钟
- 30 天内合并 PR
- 352
环境准备
在浏览器里用你自己的 GitHub 账号启动这个项目的开发容器。
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
pingdotgg/t3code 的其他 Issue
-
难度 2/5 1-3 小时 新手友好度 84/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 78/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 78/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 67/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 82/100
维护者通常 1 天内回复
相似的 Issue
-
Autocomplete still accepts an option after becoming disabled可能已有人在做 @Xapollon430 今天认领。 未关闭
难度 2/5 1-3 小时 新手友好度 67/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 78/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 78/100
维护者通常 1 天内回复
-
难度 1/5 1-3 小时 新手友好度 78/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 72/100
reduxjs/react-redux#2416 · 1 条评论 ·