fix: resolve 12 bugs across API, MCP, agent tools, web search, and context overflow (#674)
* fix: resolve 12 bugs across API, MCP, agent tools, web search, and context overflow API fixes: - Fix Gemini 400 error: delete 'store: false' field for Gemini endpoints (was globally injected, Gemini rejects unknown fields) - Fix session timeout 500 errors after ~25min: add 120s idle timeout on SSE stream readers in openaiShim and codexShim to detect dead connections and trigger withRetry reconnection - Fix context overflow 500 errors: add handler in errors.ts for 500 responses caused by oversized conversation context (too many tokens), surfacing user-friendly message with recovery actions instead of raw 'API Error: 500' Agent loop fix: - Fix premature task completion: detect continuation signals like 'so now I have to do it' in assistant text without tool calls and inject a meta nudge to force the agent to continue Web search improvements: - Increase result counts: Bing/Tavily/Exa/Firecrawl from 10→15, Mojeek/You/Jina from default→10 (explicit), max_uses 8→15 MCP fixes: - Reduce default tool timeout from ~27.8 hours to 5 minutes (tools no longer hang indefinitely on unresponsive servers) - Add retry logic (3 attempts) for tools/list fetch failures (prevents all MCP tools from silently disappearing on timeout) - Add abort signal check in URL elicitation retry loop - Improve MCP error messages with server and tool name context Agent tool fixes: - Fix SendMessage race condition: double-check task status before auto-resuming stopped agents to prevent duplicate registration - Fix auto-compact circuit breaker gap: when auto-compact fails 3+ consecutive times, proactively block oversized context BEFORE the API call instead of letting it 500. Clear message with recovery instructions (/new, /compact, rewind). Tests: 850 total, 0 failures (25 new bugfix tests) * fix: address all 4 review blockers + 6 additional issues from PR #674 Blockers (from Vasanthdev2004 review): 1. Continuation nudge infinite loop — no loop guard Added continuationNudgeCount to State, capped at MAX_CONTINUATION_NUDGES (3). Counter increments on each nudge, resets on tool execution (next_turn). 2. Continuation signal regexes too broad — high false-positive rate Tightened all patterns to require explicit action verbs. Added completion marker check (done/finished/completed/summary). Broad patterns only fire on messages <80 chars. 3. BUGFIXES.md in repo root — scope contamination Removed. PR description already contains this info. 4. AgentTool dump state cleanup is comment-only, not a bug fix Wrapped clearInvokedSkillsForAgent and clearDumpState in individual try/catch blocks so one failure doesn't prevent the other. Additional issues: 5+6. readWithTimeout ignores AbortSignal, timer leak on abort Added optional signal param to openaiStreamToAnthropic, codexStreamToAnthropic, collectCodexCompletedResponse, readSseEvents. Added abort listener that clears idle timer so AbortError surfaces cleanly instead of spurious idle timeout. 7. MCP error format change breaks consumers Reverted human-readable message to original errorDetails format. Moved server/tool context to telemetryMessage param only. 10. AgentTool test broken by comment change Updated test assertions to match new defensive cleanup text + try/catch. 12. Mojeek test regex dangerously broad Tightened to match searchParams.set('t', '10') specifically. 14. linkup.ts in providerCounts test — no result count field Removed from providers list (uses depth param, not result count). 15. Error message overlap between errors.ts and query.ts Prefixed errorDetails with 'Context overflow (500):' to distinguish. Tests: 851 pass, 0 fail --------- Co-authored-by: openclaude-bot <bot@openclaude.ai> Co-authored-by: Fix Bot <fix@openclaude.dev>
This commit is contained in:
@@ -206,9 +206,12 @@ export function isMcpSessionExpiredError(error: Error): boolean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Default timeout for MCP tool calls (effectively infinite - ~27.8 hours).
|
||||
* Default timeout for MCP tool calls (5 minutes — reasonable for most tools).
|
||||
* Use MCP_TOOL_TIMEOUT env var to override per-server.
|
||||
* The previous default of ~27.8 hours effectively meant no timeout, causing
|
||||
* tools to hang indefinitely on unresponsive servers.
|
||||
*/
|
||||
const DEFAULT_MCP_TOOL_TIMEOUT_MS = 100_000_000
|
||||
const DEFAULT_MCP_TOOL_TIMEOUT_MS = 300_000
|
||||
|
||||
/**
|
||||
* Cap on MCP tool descriptions and server instructions sent to the model.
|
||||
@@ -1764,10 +1767,32 @@ export const fetchToolsForClient = memoizeWithLRU(
|
||||
return []
|
||||
}
|
||||
|
||||
const result = (await client.client.request(
|
||||
{ method: 'tools/list' },
|
||||
ListToolsResultSchema,
|
||||
)) as ListToolsResult
|
||||
// Retry tool list fetch up to 2 times on transient failures.
|
||||
// Without retry, a single timeout during tools/list makes all MCP tools
|
||||
// silently disappear from the model's context until the next reconnect.
|
||||
let result: ListToolsResult | undefined
|
||||
let lastError: unknown
|
||||
for (let attempt = 0; attempt < 3; attempt++) {
|
||||
try {
|
||||
result = (await client.client.request(
|
||||
{ method: 'tools/list' },
|
||||
ListToolsResultSchema,
|
||||
)) as ListToolsResult
|
||||
break
|
||||
} catch (err) {
|
||||
lastError = err
|
||||
if (attempt < 2) {
|
||||
logMCPDebug(
|
||||
client.name,
|
||||
`tools/list failed (attempt ${attempt + 1}/3): ${errorMessage(err)}. Retrying...`,
|
||||
)
|
||||
await sleep(1000 * (attempt + 1))
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!result) {
|
||||
throw lastError ?? new Error('tools/list failed after 3 attempts')
|
||||
}
|
||||
|
||||
// Sanitize tool data from MCP server
|
||||
const toolsToProcess = recursivelySanitizeUnicode(result.tools)
|
||||
@@ -2864,6 +2889,11 @@ export async function callMCPToolWithUrlElicitationRetry({
|
||||
}): Promise<MCPToolCallResult> {
|
||||
const MAX_URL_ELICITATION_RETRIES = 3
|
||||
for (let attempt = 0; ; attempt++) {
|
||||
// Check abort signal before each attempt — without this, a cancelled
|
||||
// elicitation retry loop continues spinning until MAX retries
|
||||
if (signal.aborted) {
|
||||
throw new Error('Tool call aborted during URL elicitation')
|
||||
}
|
||||
try {
|
||||
return await callToolFn({
|
||||
client: connectedClient,
|
||||
@@ -3156,9 +3186,12 @@ async function callMCPTool({
|
||||
errorDetails = String(result.error)
|
||||
}
|
||||
logMCPError(name, errorDetails)
|
||||
// Include server and tool name in telemetry for debugging, but keep
|
||||
// the human-readable message unchanged to avoid breaking error consumers
|
||||
// that parse the message string.
|
||||
throw new McpToolCallError_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS(
|
||||
errorDetails,
|
||||
'MCP tool returned error',
|
||||
`MCP tool [${name}] ${tool}: ${errorDetails}`,
|
||||
'_meta' in result && result._meta ? { _meta: result._meta } : undefined,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user