MCPcopy Index your code
hub / github.com/codeaashu/claude-code / startStallWatchdog

Function startStallWatchdog

src/tasks/LocalShellTask/LocalShellTask.tsx:46–104  ·  view source on GitHub ↗
(taskId: string, description: string, kind: BashTaskKind | undefined, toolUseId?: string, agentId?: AgentId)

Source from the content-addressed store, hash-verified

44// Output-side analog of peekForStdinData (utils/process.ts): fire a one-shot
45// notification if output stops growing and the tail looks like a prompt.
46function startStallWatchdog(taskId: string, description: string, kind: BashTaskKind | undefined, toolUseId?: string, agentId?: AgentId): () => void {
47 if (kind === 'monitor') return () => {};
48 const outputPath = getTaskOutputPath(taskId);
49 let lastSize = 0;
50 let lastGrowth = Date.now();
51 let cancelled = false;
52 const timer = setInterval(() => {
53 void stat(outputPath).then(s => {
54 if (s.size > lastSize) {
55 lastSize = s.size;
56 lastGrowth = Date.now();
57 return;
58 }
59 if (Date.now() - lastGrowth < STALL_THRESHOLD_MS) return;
60 void tailFile(outputPath, STALL_TAIL_BYTES).then(({
61 content
62 }) => {
63 if (cancelled) return;
64 if (!looksLikePrompt(content)) {
65 // Not a prompt — keep watching. Reset so the next check is
66 // 45s out instead of re-reading the tail on every tick.
67 lastGrowth = Date.now();
68 return;
69 }
70 // Latch before the async-boundary-visible side effects so an
71 // overlapping tick's callback sees cancelled=true and bails.
72 cancelled = true;
73 clearInterval(timer);
74 const toolUseIdLine = toolUseId ? `\n<${TOOL_USE_ID_TAG}>${toolUseId}</${TOOL_USE_ID_TAG}>` : '';
75 const summary = `${BACKGROUND_BASH_SUMMARY_PREFIX}"${description}" appears to be waiting for interactive input`;
76 // No <status> tag — print.ts treats <status> as a terminal
77 // signal and an unknown value falls through to 'completed',
78 // falsely closing the task for SDK consumers. Statusless
79 // notifications are skipped by the SDK emitter (progress ping).
80 const message = `<${TASK_NOTIFICATION_TAG}>
81<${TASK_ID_TAG}>${taskId}</${TASK_ID_TAG}>${toolUseIdLine}
82<${OUTPUT_FILE_TAG}>${outputPath}</${OUTPUT_FILE_TAG}>
83<${SUMMARY_TAG}>${escapeXml(summary)}</${SUMMARY_TAG}>
84</${TASK_NOTIFICATION_TAG}>
85Last output:
86${content.trimEnd()}
87
88The command is likely blocked on an interactive prompt. Kill this task and re-run with piped input (e.g., \`echo y | command\`) or a non-interactive flag if one exists.`;
89 enqueuePendingNotification({
90 value: message,
91 mode: 'task-notification',
92 priority: 'next',
93 agentId
94 });
95 }, () => {});
96 }, () => {} // File may not exist yet
97 );
98 }, STALL_CHECK_INTERVAL_MS);
99 timer.unref();
100 return () => {
101 cancelled = true;
102 clearInterval(timer);
103 };

Callers 3

spawnShellTaskFunction · 0.85
backgroundTaskFunction · 0.85

Calls 6

getTaskOutputPathFunction · 0.85
statFunction · 0.85
tailFileFunction · 0.85
looksLikePromptFunction · 0.85
escapeXmlFunction · 0.85

Tested by

no test coverage detected