{"slug": "one-line-of-math-froze-my-ai-agent-forever-the-timeout-watched-and-did-nothing", "title": "One line of math froze my AI agent forever. The timeout watched and did nothing.", "summary": "A developer discovered that a single line of math, `10 ** 10 ** 8`, can freeze Hugging Face's smolagents AI agent framework indefinitely because CPython's arbitrary-precision exponentiation holds the GIL in a single C call, preventing thread-based timeouts from firing. The bug, reported in issue #2473, affects the LocalPythonExecutor's timeout mechanism, leaving the process unresponsive until externally killed. The developer added a watchdog supervisor to detect and report such freezes, and Sentry's Seer root cause analysis independently confirmed the issue.", "body_md": "*This is my second entry for the DEV x Sentry Bug Smash challenge. Entry #1 was a crash with a confusing error message. This one is the opposite and it is scarier: no crash, no message, no event. Just silence.*\n\nsmolagents runs LLM-generated Python in a sandboxed executor with a timeout. Issue [#2473](https://github.com/huggingface/smolagents/issues/2473) claims that one line of model-generated math defeats it completely:\n\n``` python\nfrom smolagents.local_python_executor import LocalPythonExecutor\n\nexecutor = LocalPythonExecutor(additional_authorized_imports=[], timeout_seconds=2)\nexecutor.send_tools({})\nexecutor(\"10 ** 10 ** 8\")  # 2 second timeout. Should be fine, right?\n```\n\nI ran this with a 2 second timeout and a faulthandler bomb set for 20 seconds. The timeout never fired. The process sat frozen until the external kill. An agent that generates this expression (and \"compute this huge number\" is exactly the kind of thing agents try) freezes its host process forever.\n\nZero comments on the issue, zero PRs. Mine now.\n\nsmolagents' timeout is thread based: a worker thread runs the code, the main thread waits in `future.result(timeout=2)`\n\n. That design is fine for almost everything, because CPython switches threads between bytecode instructions.\n\nBut `10 ** 10 ** 8`\n\nis not \"almost everything\". CPython computes arbitrary precision `**`\n\n, `<<`\n\nand `*`\n\ninside a single C call that holds the GIL from start to finish. No bytecode boundary, no thread switch, no timeout. The result would have about 400 million bits. The computation takes somewhere between minutes and hours. Your watchdog needs the GIL to wake up, and it never gets it.\n\nThe faulthandler dump made it concrete, and it was worse than the issue described:\n\n```\nThread 0x16d1f3000 (worker):\n  File \"local_python_executor.py\", line 753 in evaluate_binop   <- computing the pow\n\nThread 0x1f00d5e80 (main):\n  File \"threading.py\", line 999 in start\n  File \"concurrent/futures/thread.py\", line 180 in submit       <- never returned!\n```\n\nThe main thread was still stuck inside `ThreadPoolExecutor.submit`\n\n. It never even reached `future.result`\n\n. The 2 second timer never armed at all.\n\nThe existing `MAX_OPERATIONS`\n\nguard (10 million AST operations) does not help either. This is a handful of AST nodes. The entire cost lives inside one of them.\n\nEntry #1 was about a noisy failure. This bug is the opposite. I pointed a fresh Sentry project at a simulated agent run on the unpatched PyPI release (1.26.0) and got the most unsettling result possible: nothing. No event, no transaction, an empty project. The process was frozen mid-transaction and the SDK never got a chance to flush.\n\nThe lesson: for freeze-class bugs you need a supervisor. I added a small watchdog process that gives the worker 25 seconds, then kills it and reports what it saw, with the worker's faulthandler stack attached as evidence:\n\n```\nwatchdog: starting worker (before) with 25s budget\nworker: smolagents 1.26.0\nworker: step 1 executing 'result = 10 ** 10 ** 8'...\nwatchdog: worker FROZE, killed it, reporting to Sentry\n```\n\nThe resulting Sentry issue carries the whole story in one place: the frozen frame (`evaluate_binop`\n\n, line 753 of `local_python_executor.py`\n\n) sits right in the attached `worker_faulthandler_stack`\n\nextra, environment tagged `before`\n\n, handled by nothing except the supervisor.\n\nSentry's Seer ran root cause analysis on that issue and independently landed on the same conclusion: signal and thread interruption need the GIL, and a single C-level big-int operation never releases it.\n\nSeer's verdict, verbatim:\n\nCodeAgent's 2s timeout uses Python signal-based interruption, which cannot fire during uninterruptible C-level big-int operations that hold the GIL. [...] A single large big-integer arithmetic operation runs entirely as one C-level call that holds the GIL continuously without yielding. CPython cannot deliver signals or switch threads during an uninterruptible C extension call, so no timeout callback fires for the duration of that operation.\n\nKilling the computation mid-flight is impossible from Python. But predicting the damage is O(1). Before executing `**`\n\n, `<<`\n\nor `*`\n\non integers, the executor now estimates the result's bit length from the operands' bit lengths:\n\n```\nif op == \"**\":\n    estimated_bits = left.bit_length() * right       # upper bound\nelif op == \"<<\":\n    estimated_bits = left.bit_length() + right\nelif op == \"*\":\n    estimated_bits = left.bit_length() + right.bit_length()\n```\n\nAbove 1 million bits (about 300k digits, still generous) it raises an informative `InterpreterError`\n\n:\n\n```\nOperation '**' would produce an integer of around 400000000 bits, exceeding\nthe maximum of 1000000 bits allowed. Use smaller operands, or\npow(base, exp, mod) for modular exponentiation.\n```\n\nThat message matters. The agent surfaces it to the model, and the model can actually act on it: use `pow(base, exp, mod)`\n\n, which stays unrestricted because modular exponentiation is fast and legitimate. The agent recovers on the next step instead of hanging the host.\n\nThe after run on the patched build: the guard rejects the expression in 0.0 seconds, the error lands in Sentry as a normal actionable issue and step 2 executes fine.\n\n```\nwatchdog: starting worker (after) with 25s budget\nworker: smolagents 1.27.0.dev0\nworker: step 1 error surfaced to the model: InterpreterError: ...\nworker: step 2 executing '2 + 2'...\nworker: step 2 ok\nworker: DONE\nwatchdog: worker exited with code 0\n```\n\nMinutes after I opened the PR, OpenAI's Codex reviewer flagged a real hole: my guard checked `type(x) is int`\n\n, which lets `bool`\n\nand `int`\n\nsubclasses slip through. `True << 10**9`\n\nand `class BigInt(int)`\n\nstill reached the uninterruptible C calls. Fixed with `isinstance`\n\n, added both as regression tests. AI found the bug class, AI fixed it, AI reviewed the fix. I just steered.\n\n`**`\n\n, `<<`\n\n, chained `*`\n\n, all augmented forms, `pow(a, b)`\n\n, bool and int subclass variants`*=`\n\n, `pow(7, 2**64, 97)`\n\n, float pow, `1 ** 10**9`\n\nThe scariest bugs are not the ones that page you at 3am. They are the ones that make sure nothing ever pages you at all. Instrument for silence.", "url": "https://wpnews.pro/news/one-line-of-math-froze-my-ai-agent-forever-the-timeout-watched-and-did-nothing", "canonical_source": "https://dev.to/himanshu_748/one-line-of-math-froze-my-ai-agent-forever-the-timeout-watched-and-did-nothing-2dma", "published_at": "2026-07-19 07:54:58+00:00", "updated_at": "2026-07-19 08:27:42.491290+00:00", "lang": "en", "topics": ["ai-agents", "ai-safety", "developer-tools"], "entities": ["smolagents", "Hugging Face", "Sentry", "Seer", "LocalPythonExecutor", "CPython", "GitHub"], "alternates": {"html": "https://wpnews.pro/news/one-line-of-math-froze-my-ai-agent-forever-the-timeout-watched-and-did-nothing", "markdown": "https://wpnews.pro/news/one-line-of-math-froze-my-ai-agent-forever-the-timeout-watched-and-did-nothing.md", "text": "https://wpnews.pro/news/one-line-of-math-froze-my-ai-agent-forever-the-timeout-watched-and-did-nothing.txt", "jsonld": "https://wpnews.pro/news/one-line-of-math-froze-my-ai-agent-forever-the-timeout-watched-and-did-nothing.jsonld"}}