The Signal Nobody Heard, Fixing a Silent AbortSignal Bug in OpenClaw A developer fixed a bug in OpenClaw, an open-source personal AI assistant, where the fetchWithTimeout utility silently discarded caller-provided AbortSignal objects, causing requests to continue running after cancellation. The fix chains the caller's signal to the internal timeout signal so that both can abort the request. This is a submission for DEV's Summer Bug Smash: Clear the Lineup powered by Sentry. Every codebase has that one bug that looks tiny on paper and behaves like a small landmine underneath. This is the story of one of those bugs, and the fix I shipped for it in OpenClaw https://github.com/openclaw/openclaw , a large open source personal AI assistant project. OpenClaw is a cross platform personal AI assistant written mostly in TypeScript. It runs as a gateway process that connects large language models to real communication channels such as Telegram, Slack, Discord, and Apple Messages, while also handling sessions, memory, plugins, and tool calls for agents working on your behalf. Because the gateway is the thing sitting between a user's message and a model's response, it lives and dies by how well it manages network requests. Every outbound call, whether it is talking to a model provider, a channel API, or an internal service, gets wrapped with a shared timeout utility so nothing hangs the event loop forever. That utility is called fetchWithTimeout , and it is used across dozens of call sites in the codebase. I am aniruddhaadak80 on GitHub, and this project is one of the places where I have been sending small, focused fixes rather than one giant rewrite. You can see the full list of my open source work on my GitHub profile https://github.com/aniruddhaadak80 . Here is the problem in plain words. fetchWithTimeout accepts a normal RequestInit object as one of its parameters, the same shape you would pass to the native fetch function. A caller can put anything in there, including their own AbortSignal , if they already have a reason to cancel the request early. Maybe the user pressed stop. Maybe the session ended. Maybe a parent operation was cancelled and everything underneath it should stop too. The trouble was in how the utility built its own internal signal for the timeout. Internally it spread the caller's init object first, and then added its own timeout signal on top of it. That ordering meant the caller's signal was silently thrown away every single time. Whatever cancellation logic the calling code thought it had wired up simply did not exist anymore once the request reached fetchWithTimeout . The practical effect was not a crash. It was quieter and arguably worse. A request that the rest of the system believed had been cancelled would keep running in the background until either it finished naturally or the internal timeout eventually caught up with it. Under normal load you would not notice. Under real load, with many concurrent sessions and channels, this is exactly the kind of leak that piles up, ties up connections, and makes the gateway feel sluggish for reasons that are hard to trace back to a root cause. Here is the pull request with the actual change. In src/utils/fetch-timeout.ts , the fetchWithTimeout wrapper is implemented as follows: export async function fetchWithTimeout url: string, init: RequestInit, timeoutMs: number, fetchFn: typeof fetch = fetch, : Promise