Notes from a 2 AM On-Call: Silent Agent Timeout in pstack-claude's Cursor Harness
The Failure Symptom
[2026-01-14T02:17:33Z] Agent workflow started: refactor_auth_middleware [2026-01-14T02:18:03Z] <no output> [2026-01-14T02:18:04Z] Next workflow queued: update_tests No error. No traceback. No stderr. The Cursor agent runner in michael-denyer/pstack-claude just...stopped executing after exactly 30 seconds. The next workflow in the queue picked up immediately, but the refactor job silently dropped. Production requests from our FastAPI gateway started piling up in Redis, and Sentry showed zero exceptions.
This wasn't a crash—it was a silent timeout cliff.
The Red Herring
Every Cursor and agent automation tutorial tells you to configure request_timeout in your IDE settings or .cursorrules. We had that set to 120 seconds. The official pstack examples show:
{ "cursor.timeout": 120000, "cursor.maxTokens": 8192 } Looks fine, right? When I tested the same refactor workflow manually through Cursor's UI, it completed in 45 seconds without issue. But when triggered programmatically via pstack-claude's PotentoCursorAgent harness under concurrent load (6 parallel workflows), it hard-stopped at 30s.
The problem? Nested timeout inheritance in agent execution contexts.
The Root Cause
Digging through michael-denyer/pstack-claude's agent runner implementation, I found this in src/agents/cursor_agent.py:
# Before: Implicit default from httpx client response = await self.client.post( f"{self.base_url}/v1/chat/completions", json=payload, # No explicit timeout—inherits httpx default of 30s ) The cursor.timeout setting in IDE config controls the UI streaming timeout. It has zero effect on the programmatic HTTP client timeout used by pstack's agent harness when calling the LLM gateway (in our case, routed through https://b-lost.com/v1 for multi-provider fallback).
Under load, our refactor workflow hit a complex dependency graph that triggered 4 sequential sub-agent calls. Each call took ~8 seconds, totaling 32 seconds—just past httpx's 30s default. The client silently closed the connection, and pstack's error handling swallowed the timeout exception without logging.
The Runnable Fix
Three-line patch to src/agents/cursor_agent.py:
import httpx # Explicit timeout for programmatic agent execution self.client = httpx.AsyncClient( timeout=httpx.Timeout(120.0, connect=10.0), limits=httpx.Limits(max_keepalive_connections=10) ) response = await self.client.post( f"{self.base_url}/v1/chat/completions", json=payload, ) We also added explicit error surfacing:
try: response = await self.client.post(...) response.raise_for_status() except httpx.TimeoutException as e: logger.error(f"Agent timeout after {e.request.extensions['timeout']}: {payload['messages'][-1]['content'][:100]}") raise After deploying this patch, the refactor workflow completed in 38 seconds, and our Redis queue depth dropped to zero within 5 minutes.
Performance Impact
Before the fix (6 concurrent workflows over 10 minutes):
- Silent failures: 14 out of 60 workflows (23%)
- Average queue depth: 8.3 requests
- P95 end-to-end latency: 140s (inflated by retries)
After the fix:
- Silent failures: 0
- Average queue depth: 0.4 requests
- P95 end-to-end latency: 42s
The Invariant Rule
Never rely on implicit timeouts in production agent harnesses.
When integrating pstack-claude (or any LLM workflow orchestrator) with external APIs—especially multi-provider relay gateways like B-Lost—set explicit httpx.Timeout values tied to your SLA budget, not client library defaults. If your P95 workflow execution is 45 seconds, your HTTP timeout should be at least 90 seconds with connection timeout clamped to 10s.
And always, always log timeout exceptions with enough context to reconstruct the failing request payload.
Configuration Reference
Full production-grade config for pstack-claude's Cursor agent runner:
from httpx import AsyncClient, Timeout, Limits agent_client = AsyncClient( base_url="https://b-lost.com/v1", # Multi-provider relay timeout=Timeout(120.0, connect=10.0), limits=Limits( max_connections=20, max_keepalive_connections=10, keepalive_expiry=30.0 ), headers={"X-Workflow-ID": workflow_id} # For trace correlation ) This configuration survived our Black Friday load test: 2,400 concurrent agent workflows over 6 hours with zero silent timeouts.
When to Worry
You're hitting this same failure mode if:
- Your agent workflows succeed in manual testing but fail silently under concurrent execution
- Logs show workflow start events but no completion or error events
- Execution time clusters tightly around 30s, 60s, or other round timeout boundaries
- Adding
time.sleep(5)to your workflow logic shifts the failure point by exactly 5 seconds
If you see these symptoms in michael-denyer/pstack-claude or similar agent harnesses (Antigravity, Claude Code, Codex variants), audit every HTTP client instantiation for missing explicit timeout configuration.
The 2 AM pages stopped after this fix shipped. Highly recommend the same audit for any production LLM orchestration stack.
