16b476a205
A transient network blip during an external-MCP handshake left createMCPClient pending forever (@ai-sdk/mcp does not settle on abort). getOrBuildEntry caches the per-workspace build PROMISE, so the never-settling connect poisoned the cache and every later turn hung at step_count=0 before streamText — the run never finalized, the row stayed 'running', and the chat was permanently blocked with A_RUN_ALREADY_ACTIVE (an explicit Stop could not interrupt the un-signalled setup). - mcp-clients: wrap connect() in a settling timeout (connectWithTimeout) that closes a late-arriving client, so a hung handshake rejects instead of poisoning the build cache; the bad server is skipped and the build completes. - mcp-clients: close a connected-but-unregistered client when tools() fails, fixing a pre-existing transport leak in buildEntry. - ai-chat.service: bound the toolsFor build with the run's abort signal AND a deadline (raceAgainstAbortAndTimeout); re-throw an explicit Stop only when a run exists (runId) so the run finalizes as 'aborted', and keep the legacy socket-bound path unchanged; settle the run 'aborted' vs 'error' accordingly. - tests: cache-not-poisoned + orphan-client-close-once + Stop-during-setup finalizes the run once. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>