fix(mcp): keep wait_for_task_change alive past Claude Code's idle timeout
MCP_TOOL_TIMEOUT (raised by ClaudeDo's launchers) is a wall-clock cap unrelated to Claude Code's idle-silence abort (default 300s for HTTP-transport MCP servers), which no launcher raises. A wait near the 900s recommendation was silently killed at ~300s in any session, launcher or not. WaitForTaskChange now reports MCP progress every 30s while polling, which resets that idle timer. Verified against a real claude -p call (no launcher env overrides) surviving a 341s wait via 60s pings -- the same silence that previously aborted at 300s. Tool description and the list-handler prompt no longer claim ClaudeDo's launchers guarantee a long wait survives.
This commit is contained in:
@@ -4,6 +4,7 @@ using ClaudeDo.Data.Models;
|
||||
using ClaudeDo.Data.Repositories;
|
||||
using ClaudeDo.Worker.External;
|
||||
using ClaudeDo.Worker.Tests.Infrastructure;
|
||||
using ModelContextProtocol;
|
||||
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
|
||||
|
||||
namespace ClaudeDo.Worker.Tests.External;
|
||||
@@ -202,4 +203,43 @@ public sealed class TaskWaitMcpToolsTests : IDisposable
|
||||
// ClaudeProcess / InteractiveLaunchSpecService set MCP_TOOL_TIMEOUT=930000ms.
|
||||
Assert.True(TaskWaitMcpTools.MaxTimeoutSeconds < 930);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WaitForTaskChange_LongWait_ReportsProgressWellUnderClaudeCodeIdleTimeout()
|
||||
{
|
||||
var original = TaskWaitMcpTools.ProgressReportInterval;
|
||||
TaskWaitMcpTools.ProgressReportInterval = TimeSpan.FromMilliseconds(200);
|
||||
try
|
||||
{
|
||||
var task = await SeedTaskAsync(TaskStatus.Running);
|
||||
var sut = BuildSut();
|
||||
var reports = new List<ProgressNotificationValue>();
|
||||
var progress = new Progress<ProgressNotificationValue>(reports.Add);
|
||||
|
||||
var result = await sut.WaitForTaskChange(
|
||||
[task.Id], timeoutSeconds: 1, progress: progress, cancellationToken: CancellationToken.None);
|
||||
|
||||
Assert.True(result.TimedOut);
|
||||
// Progress<T> marshals via the SynchronizationContext captured at construction; give
|
||||
// any queued callbacks a beat to run before asserting on `reports`.
|
||||
await Task.Delay(200);
|
||||
Assert.NotEmpty(reports);
|
||||
}
|
||||
finally
|
||||
{
|
||||
TaskWaitMcpTools.ProgressReportInterval = original;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WaitForTaskChange_NoProgressHandlerPassed_DoesNotThrow()
|
||||
{
|
||||
var task = await SeedTaskAsync(TaskStatus.Running);
|
||||
var sut = BuildSut();
|
||||
|
||||
var result = await sut.WaitForTaskChange(
|
||||
[task.Id], timeoutSeconds: 1, progress: null, cancellationToken: CancellationToken.None);
|
||||
|
||||
Assert.True(result.TimedOut);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user