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:
FluxLuFFy
2026-04-14 16:29:53 +05:30
committed by GitHub
parent 1741f32cb7
commit 25ce2ca7bf
18 changed files with 647 additions and 27 deletions

View File

@@ -1042,10 +1042,12 @@ export const AgentTool = buildTool({
});
} finally {
stopBackgroundedSummarization?.();
clearInvokedSkillsForAgent(syncAgentId);
clearDumpState(syncAgentId);
// Note: worktree cleanup is done before enqueueAgentNotification
// in both try and catch paths so we can include worktree info
// Defensive cleanup: wrap each call so one failure doesn't
// prevent the other from running. Without this, if
// clearInvokedSkillsForAgent throws, clearDumpState is
// skipped and dump state leaks.
try { clearInvokedSkillsForAgent(syncAgentId); } catch { /* cleanup best-effort */ }
try { clearDumpState(syncAgentId); } catch { /* cleanup best-effort */ }
}
});

View File

@@ -819,7 +819,25 @@ export const SendMessageTool: Tool<InputSchema, SendMessageToolOutput> =
},
}
}
// task exists but stopped — auto-resume
// task exists but stopped — auto-resume.
// Guard against race: two concurrent SendMessage calls to the same
// stopped agent could both trigger resumeAgentBackground(), causing
// duplicate task registration. Check status again after acquiring
// the task reference (the first resume changes status to 'running').
const freshTask = context.getAppState().tasks[agentId]
if (isLocalAgentTask(freshTask) && freshTask.status === 'running') {
queuePendingMessage(
agentId,
input.message,
context.setAppStateForTasks ?? context.setAppState,
)
return {
data: {
success: true,
message: `Message queued for delivery to ${input.to} at its next tool round (was concurrently resumed).`,
},
}
}
try {
const result = await resumeAgentBackground({
agentId,

View File

@@ -125,7 +125,7 @@ function makeToolSchema(input: Input): BetaWebSearchTool20250305 {
name: 'web_search',
allowed_domains: input.allowed_domains,
blocked_domains: input.blocked_domains,
max_uses: 8, // Hardcoded to 8 searches maximum
max_uses: 15, // Allow up to 15 searches per query for better coverage
}
}

View File

@@ -19,7 +19,7 @@ export const bingProvider: SearchProvider = {
const url = new URL('https://api.bing.microsoft.com/v7.0/search')
url.searchParams.set('q', input.query)
url.searchParams.set('count', '10')
url.searchParams.set('count', '15')
const res = await fetch(url.toString(), {
headers: { 'Ocp-Apim-Subscription-Key': process.env.BING_API_KEY! },

View File

@@ -19,7 +19,7 @@ export const exaProvider: SearchProvider = {
const body: Record<string, any> = {
query: input.query,
numResults: 10,
numResults: 15,
type: 'auto',
}

View File

@@ -21,7 +21,7 @@ export const firecrawlProvider: SearchProvider = {
query = `${query} ${exclusions}`
}
const data = await app.search(query, { limit: 10 })
const data = await app.search(query, { limit: 15 })
const hits = applyDomainFilters(
(data.web ?? []).map((r: { url: string; title?: string; description?: string }) => ({

View File

@@ -19,6 +19,7 @@ export const jinaProvider: SearchProvider = {
const url = new URL('https://s.jina.ai/')
url.searchParams.set('q', input.query)
url.searchParams.set('count', '10')
const res = await fetch(url.toString(), {
headers: {

View File

@@ -26,6 +26,7 @@ export const linkupProvider: SearchProvider = {
body: JSON.stringify({
q: input.query,
search_type: 'standard',
depth: 'standard',
}),
signal,
})

View File

@@ -20,6 +20,7 @@ export const mojeekProvider: SearchProvider = {
const url = new URL('https://www.mojeek.com/search')
url.searchParams.set('q', input.query)
url.searchParams.set('fmt', 'json')
url.searchParams.set('t', '10')
const headers: Record<string, string> = {
'Accept': 'application/json',

View File

@@ -25,7 +25,7 @@ export const tavilyProvider: SearchProvider = {
},
body: JSON.stringify({
query: input.query,
max_results: 10,
max_results: 15,
include_answer: false,
}),
signal,

View File

@@ -19,6 +19,7 @@ export const youProvider: SearchProvider = {
const url = new URL('https://api.ydc-index.io/v1/search')
url.searchParams.set('query', input.query)
url.searchParams.set('num_web_results', '10')
const res = await fetch(url.toString(), {
headers: { 'X-API-Key': process.env.YOU_API_KEY! },