Skip to content

Commit e385422

Browse files
brookscclaude
andcommitted
fix(autofire): reset miss counter when agent produces output during tool call
The escalation path (10 misses → drop notification) was firing prematurely when the coordinator was mid-tool-call: no ❯ visible, but the agent was actively working. The interval now tracks the previous tick's tail — if new output arrived the miss count resets to zero. Escalation only triggers when the tail has been completely static for 10+ seconds with no prompt visible. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 85611de commit e385422

2 files changed

Lines changed: 13 additions & 7 deletions

File tree

TODOS.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,9 @@
1111

1212
## Known edge cases — no fix yet
1313

14-
### 7. Autofire expiry window — coordinator in long tool call during countdown
14+
### ~~7. Autofire expiry window — coordinator in long tool call during countdown~~ ✅ COMPLETE
1515

16-
**What's wrong:** If the coordinator is mid-tool-call when autofire countdown fires, it finds no PTY prompt, misses 10 times, and escalates unnecessarily.
17-
**No fix yet** — depends on timing; low frequency in practice.
16+
Fixed: the interval now tracks the last-seen tail. If new output arrives since the previous tick the agent is actively working, so the miss counter resets to zero. Escalation only triggers when the tail has been completely static for 10+ consecutive seconds with no prompt visible.
1817

1918
### 8. Post-restart coordinator MCP config stale if coordinator process restarts
2019

src/components/PromptInput.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,7 @@ export function PromptInput(props: PromptInputProps) {
421421
lastStagedText = notification.text;
422422
autoFirePromptMissCount = 0;
423423
setText(notification.text);
424+
let lastIntervalTail = '';
424425

425426
// eslint-disable-next-line solid/reactivity -- intentional untracked reads in interval
426427
autoFireInterval = window.setInterval(() => {
@@ -437,12 +438,20 @@ export function PromptInput(props: PromptInputProps) {
437438
autoFireInterval = undefined;
438439
return;
439440
}
441+
const currentTail = stripAnsi(untrack(() => getAgentOutputTail(props.agentId)));
442+
// If the agent produced new output since the last tick it is actively working
443+
// (e.g. mid-tool-call). Reset the miss counter so a long tool call doesn't
444+
// accumulate misses and trigger the escalation path prematurely.
445+
if (currentTail !== lastIntervalTail) {
446+
autoFirePromptMissCount = 0;
447+
lastIntervalTail = currentTail;
448+
}
440449
const tick = processAutoFireTick({
441450
staged,
442451
now: Date.now(),
443452
controlledBy: untrack(() => store.tasks[props.taskId]?.controlledBy),
444453
questionActive: untrack(() => questionActive()),
445-
tail: stripAnsi(untrack(() => getAgentOutputTail(props.agentId))),
454+
tail: currentTail,
446455
currentMissCount: autoFirePromptMissCount,
447456
});
448457

@@ -458,9 +467,7 @@ export function PromptInput(props: PromptInputProps) {
458467
}
459468
if (tick.outcome === 'no-prompt') {
460469
autoFirePromptMissCount = tick.newMissCount;
461-
const tailSnippet = stripAnsi(untrack(() => getAgentOutputTail(props.agentId))).slice(
462-
-PROMPT_MARKER_SCAN_CHARS,
463-
);
470+
const tailSnippet = currentTail.slice(-PROMPT_MARKER_SCAN_CHARS);
464471
if (autoFirePromptMissCount === 1 || autoFirePromptMissCount % 5 === 0) {
465472
logWarn('autofire', 'prompt not detected after autoFireAt', {
466473
taskId: props.taskId,

0 commit comments

Comments
 (0)