loopstray
Detects LangChain AgentExecutor(...) (and
AgentExecutor.from_agent_and_tools(...)) construction calls with no
max_execution_time and/or no max_iterations set -- a poorly-behaved
agent stuck in an unconverged reasoning loop can run far longer, and cost
far more, than it needs to before anything stops it. Zero runtime
dependencies, pure ast-based static analysis -- it never imports or
executes the scanned code.
Why
A LangChain AgentExecutor runs a ReAct-style loop: reason, call a tool,
observe the result, reason again, until the agent decides it's done. A
demo with a handful of test prompts converges quickly every time -- so it
ships without either cap ever being set. Then a real user sends a request
the agent genuinely can't resolve, and instead of failing, it keeps
calling tools and re-reasoning, never converging, burning a fresh LLM API
call on every step.
This tool's own verified check of LangChain's source
(langchain_classic/agents/agent.py) found the two knobs behave
asymmetrically when left unset:
max_iterations: int | None = 15-- there is a hidden default iteration cap. Leaving it unset is not literally infinite looping.max_execution_time: float | None = None-- there is no default wall-clock cap at all. Fifteen iterations, each stuck on a slow tool call or a long reasoning step, can still run for minutes with nothing to stop it early -- often ending only when an infrastructure-level timeout finally kills the request, later and more expensively than an explicit cap would have allowed.
loopstray flags the two omissions at different severities to match this
verified, asymmetric reality, rather than treating "neither is set" as
one flat check. This is a distinct failure mode from this workspace's
llmbrittle (a single LLM call failing with no
cross-provider fallback -- the call errors out) and from Node's
stallwary (a streaming connection hanging at the network/transport
layer). loopstray is about a reasoning loop that never terminates on
its own, at the agent-framework level. See DETAILS.md for
the full, verified comparison and prior-art check.
Install
pip install loopstray
Usage
loopstray src/ # scan a directory recursively
loopstray mymodule.py # scan a single file
loopstray src/ --json # machine-readable output for CI
loopstray src/ --strict # also fail on LS002 (warning) findings
Exit codes: 0 clean, 1 an LS001 blocker is present (or any finding
under --strict), 2 usage/syntax error.
What it checks (v0.1)
| Rule | Severity | Meaning |
|---|---|---|
LS001 |
blocker | An AgentExecutor construction has no max_execution_time. No wall-clock ceiling exists at all -- LangChain's own default is unbounded time, regardless of any iteration cap. |
LS002 |
warning | An AgentExecutor construction has no max_iterations. LangChain's own hidden default of 15 still applies (not literally unbounded), but that default is invisible in this code and not pinned against future library changes. |
Suppress a line with a trailing # loopstray: ignore (or
# noqa: LS001).
Library usage
from loopstray import scan_file
findings = scan_file("mymodule.py")
for f in findings:
print(f)
See docs/USAGE.md for more, and DETAILS.md for design rationale, detection scope, and honest limitations.
License
MIT
Metadata
Release files for loopstray 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| loopstray-0.1.0.tar.gz | 14.6 kB | Without provenance | Details for loopstray-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| loopstray-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for loopstray-0.1.0-py3-none-any.whl |
Total release size: 23.6 kB
Release files / loopstray-0.1.0.tar.gz
| Download URL | loopstray-0.1.0.tar.gz |
|---|---|
| Size | 14.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b369d21f8295db08f53d6684645843868de03f48befb7ae6e07a9ad2765613d5
|
|
BLAKE2b-256 checksum How to use checksums |
fd68c12f08efd9232e215d5a2af2d6fbef7a9c5dabcc89384e5a46690c20d753
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.12
|
Release files / loopstray-0.1.0-py3-none-any.whl
| Download URL | loopstray-0.1.0-py3-none-any.whl |
|---|---|
| Size | 9.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1ee25b467e7594a57bb924364dce8aa70b1f7f8234b17eedd58e528c98543ea2
|
|
BLAKE2b-256 checksum How to use checksums |
fb1a7ebf940e87dbc7cd6d4ecf5d6dd8b3d80bdbc87699245775ce3af2df41a0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.12
|
Security for loopstray 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.