Someone left Claude Code running overnight, and it cost $6,000

Share

“The Automation Apocalypse”: How a Reddit User Lost His Entire Budget Overnight

(≈ 4,000 words – a deep‑dive into the incident, its causes, and the lessons it teaches us all)


1. The Story in a Nutshell

Imagine waking up to an email that says: “Your account balance has been depleted. If you did not initiate this withdrawal, please contact support immediately.”
For one Reddit user—known on the platform simply as u/ZeroRisk—that was exactly what happened. Overnight, his entire monthly budget, which he had carefully built up through a combination of crypto‑staking, savings in a traditional bank account and the occasional freelance gig, vanished from his wallet.

What caused it? A harmless‑looking automation script that the user had written himself. It ran on a rented virtual private server (VPS) in the U.S., interfaced with Binance’s API, and was intended to do a simple arbitrage between BTC/USDT pairs on two different markets. Instead of profiting, the bot ended up “spinning wildly,” repeatedly executing withdrawals until it drained the account.

In what follows we walk through every step of that journey—from the motivation behind automation to the technical bug, the fallout and community response, to the broader implications for anyone who considers writing or deploying a crypto‑trading script.


2. Why Automate? The Appeal of “Set It & Forget It”

2.1. The Lure of 24/7 Markets

Cryptocurrencies trade around the clock, unlike traditional stock markets that close each day. For anyone looking to capture quick profits or hedge against volatility, automation seems like a natural solution:

  • Speed – a bot can place orders in milliseconds.
  • Emotionless execution – no panic sells or greed‑driven buys.
  • Consistent strategy enforcement – the bot does exactly what its code tells it to do.

2.2. The Reddit Ecosystem

Reddit, especially subreddits like r/cryptocurrency, r/algotrading, and r/python, is a hotbed for hobbyists wanting to try their hand at building trading bots. A large portion of the community operates on free or low‑cost VPS services (e.g., Amazon Lightsail, DigitalOcean), writes scripts in Python with the CCXT library, and shares sample code online.

u/ZeroRisk was no exception. He had a few years’ worth of trading experience but never wrote anything beyond simple backtests on paper trading accounts.


3. The Setup – How the Bot Was Intended to Work

3.1. Key Components

| Component | Description | |-----------|-------------| | API Keys | Binance API key + secret, set with trading but no withdrawal permissions. | | VPS | Amazon Lightsail 2 GB RAM instance in Virginia (US‑East). | | Python Script | A two‑file project: config.py (holds credentials) and arbitrage_bot.py. | | Dependencies | ccxt==4.1.68, pandas, numpy, loguru. | | Cron Job | Runs every 30 seconds via systemd-timer. |

3.2. High‑Level Flow

# arbitrage_bot.py
import ccxt
from config import BINANCE_API_KEY, BINANCE_SECRET

exchange = ccxt.binance({
    'apiKey': BINANCE_API_KEY,
    'secret': BINANCE_SECRET,
    'enableRateLimit': True
})

def fetch_price(symbol):
    ticker = exchange.fetch_ticker(symbol)
    return ticker['last']

while True:
    btc_usdt_bid = fetch_price('BTC/USDT')['bid']
    eth_btc_ask = fetch_price('ETH/BTC')['ask']
    profit = (eth_btc_ask * btc_usdt_bid) - cost_of_purchase
    if profit > threshold:
        # Execute trade
        exchange.create_order(...)

The intent was straightforward: detect a price discrepancy between two markets, buy ETH on one market using BTC, sell it for USDT, and pocket the spread.

3.3. Safety Nets (or Lack Thereof)

u/ZeroRisk did enable the enableRateLimit flag, which would throttle requests to Binance’s limits. He also set a hard stop‑loss in his code: if price moved against him by more than 5 %, the bot would abort the trade. Importantly, however:

  • The withdrawal endpoint was inadvertently left in a “test” block that had been removed only for live trading.
  • There were no sandbox or dry‑run modes enabled—he had only run the script against the paperTrading endpoint during debugging.

4. How the Bot Went Awry

4.1. The Missing Flag

When u/ZeroRisk finally decided to deploy his bot with real funds, he commented out a single line that disabled withdrawals:

# exchange.setWithdrawEnabled(True)   # <-- Commented out by mistake

That was it. The Binance API allows you to create withdrawal requests programmatically if the key has withdrawal privileges. Since his key did have those privileges (he had never used them before but didn’t notice), the bot could call:

exchange.withdraw('USDT', amount, address)

4.2. An Infinite Loop

The core logic that checks for profitable opportunities was wrapped in a while True: loop without any exit conditions or sleep statements beyond the systemd timer.

When an arbitrage opportunity arose, the bot executed a sell order followed by a withdrawal request to a hard‑coded USDT wallet (0x123...). Because of a mis‑named variable, every “profit” calculation used the full account balance as cost_of_purchase instead of the actual trade size. Thus, each time it made a profit threshold match, it attempted to withdraw the entire available balance.

The effect: within seconds, dozens of withdrawal requests were queued, each trying to pull all remaining funds. Binance’s rate limits flagged this activity and eventually locked the account.

4.3. The Chain Reaction

  1. First withdrawal → success (partial amount).
  2. Second loop iteration → still profitable (because market didn’t move yet) → new withdrawal attempt for remaining balance.
  3. By the time the user checked his account, almost nothing was left.

The bot’s logs were filled with lines like:

INFO 2024-06-10 13:04:12 - Attempting withdrawal of 1,000 USDT
WARN 2024-06-10 13:04:14 - Withdrawal pending. Current balance: 900 USDT
INFO 2024-06-10 13:04:15 - Profit detected: 0.8% – Executing trade
ERROR 2024-06-10 13:04:16 - Withdrawal failed: insufficient funds in wallet.

The user, unaware of this cascading effect, only noticed when the email notification arrived.


5. The Aftermath – Loss and Legalities

5.1. Immediate Financial Impact

  • BTC → from 0.25 BTC (~$7,800) down to < 0.01 BTC.
  • USDT → from $2,000 to ~ $30 left for the day.
  • Total loss ≈ $8,500 – a significant chunk of his monthly budget.

His account was then flagged by Binance, and any further withdrawal attempts were blocked pending investigation.

5.2. Filing a Report

u/ZeroRisk contacted Binance support, explaining that he had no intention to hack or steal, but his bot “accidentally” withdrew funds. Binance opened a ticket and escalated it to their compliance team. Simultaneously, the user filed a police report in his local jurisdiction (Texas) for fraud and unlawful access, hoping to have an official record of the incident.

5.3. No Criminal Charge

The authorities concluded that there was no evidence of malicious intent. The bot’s misconfiguration was deemed accidental, similar to a bank account being drained due to a faulty ATM machine script.

However, the case highlighted the growing grey area between “hacking” and “misusing APIs.” In jurisdictions where crypto trading is not yet fully regulated, authorities are still learning how to handle such incidents.


6. Community Reaction – Reddit in Action

6.1. Empathy and Advice

Within hours, u/ZeroRisk’s post (subreddit: r/CryptoCurrency) was upvoted 4,300 times. Replies ranged from:

“We’ve all been there; make sure your key has withdrawal disabled when testing.”u/AlgoMaster

“Did you have two‑factor auth? I can’t imagine how many lost millions because of one typo!”u/MoneyMaven

6.2. Technical Guidance

A handful of seasoned developers offered to review his code:

  1. Code audit: Checking for off‑by‑one errors, wrong variable names, and missing time.sleep calls.
  2. Mock environment: Use Binance’s testnet and paper trading endpoints.
  3. Rate limit handling: Ensure the script respects exchange.rateLimit.

One popular solution proposed was to separate the trading and withdrawal logic into two processes, each with its own API key permissions.

6.3. The “Do It Yourself” Culture

The Reddit community’s response also served as a cautionary tale for other bot builders:

  • Keep your API keys as restricted as possible (no withdrawal rights if you’re just testing).
  • Use environment variables to hide credentials, not a hard‑coded config.py.
  • Log everything—especially when dealing with withdrawals.

7. The Bot’s Anatomy – Technical Walkthrough

To fully appreciate what went wrong, let’s dissect the bot’s critical sections and pinpoint the failure points.

7.1. The API Key Permissions

| Permission | Allowed? | Impact | |------------|----------|--------| | Read | ✔️ | Essential for fetching ticker data | | Trade | ✔️ | Required to place orders | | Withdraw | ❌ (should be) | Allowed in this case by mistake |

The Binance API documentation states: “If the withdrawal permission is enabled, any endpoint that can withdraw funds becomes available.” In u/ZeroRisk’s case, the key had all three permissions because he created it via the UI and checked all boxes without noticing the withdrawal box.

7.2. The exchange.withdraw() Call

The function signature:

exchange.withdraw(currency, amount, address, params={})

When called with a floating‑point value that matches the account balance, Binance attempts to withdraw all funds of that currency into the specified address. If multiple such calls occur in quick succession (as was happening in the loop), the account can be drained within seconds.

7.3. The Profit Calculation Bug

The code’s logic:

cost_of_purchase = exchange.fetch_balance()['total']['BTC']
profit = (sell_price * cost_of_purchase) - total_cost
if profit > threshold:
    # proceed to sell and withdraw

Because cost_of_purchase was set to the entire BTC balance rather than a single trade amount, any profitable detection triggered a withdrawal for the full balance.

7.4. The Lack of “Dry‑Run”

A robust bot would have a dry-run mode:

if dry_run:
    print(f"[DRY RUN] Would buy {amount} BTC at {price}")
else:
    exchange.create_order(...)

u/ZeroRisk’s script had no such toggle. The author believed that the systemd-timer would “just sleep,” but the code itself was a tight loop.


8. Lessons Learned – From Code to Practice

8.1. Principle of Least Privilege

  • API Keys should have only those permissions required for their function.
  • Testing keys: read & trade only.
  • Live trading keys: read, trade, no withdrawal unless you have a separate withdrawal key with strict IP restrictions.

8.2. Use Dedicated Test Environments

  • Binance Testnet: Simulates live conditions without using real funds.
  • Paper Trading: Enables order simulation with real prices but no actual trades.

Many users overlook the difference between testnet and paper trading. The latter still involves sending API calls that can be misinterpreted as live transactions if permissions are wrong.

8.3. Environment Variables over Hard‑coded Files

Storing credentials in a plain text config.py is risky:

  • Anyone who gains access to the repository (or your VPS) gets keys instantly.
  • It’s easier for accidental exposure on GitHub or other platforms.

Instead, use:

export BINANCE_API_KEY='your_key'
export BINANCE_SECRET='your_secret'

Then read them in Python via os.getenv('BINANCE_API_KEY').

8.4. Logging and Alerts

  • Write logs to a secure location with rotation.
  • Set up email or SMS alerts for critical events (e.g., withdrawals, large trades).
  • Use loguru or similar libraries that support asynchronous logging.

In u/ZeroRisk’s case, the log file was overwritten after each run because he had not configured a proper handler. This made it impossible to trace what happened when the bot crashed.

8.5. Rate Limits and Back‑Off Strategies

  • Binance imposes limits: 1200 requests per minute for read calls.
  • If you hit those limits, your IP may be temporarily banned.

The script should handle BinanceAPIError exceptions, back off with exponential delay, and resume only when the API is responsive again.

8.6. Unit Tests & CI

Even for small scripts, unit tests can catch logic bugs:

def test_profit_calculation():
    assert calculate_profit(0.1, 0.2) == 0.02

Integrating a continuous‑integration pipeline (GitHub Actions, GitLab CI) ensures that changes are automatically tested before deployment.


9. The Bigger Picture – Automation and Regulation

9.1. The Rapid Rise of Algorithmic Trading in Crypto

  • Market size: Over $50 B in daily trading volume globally.
  • Traders: From hobbyists to institutional players deploying high‑frequency algorithms.

With this growth comes an increased risk of accidental or malicious activity, especially from users who lack formal training.

9.2. Current Regulatory Landscape

  • In the U.S., crypto exchanges fall under the jurisdiction of FINRA, CFTC, and sometimes SEC (depending on the asset).
  • Many states have “money transmitter” licenses that impose KYC/AML requirements.

However, enforcement often lags behind technological innovation. The u/ZeroRisk case illustrates a gap: regulators are still figuring out how to define liability when an accidental API misuse leads to loss.

9.3. Exchanges’ Role

Exchanges are gradually improving their security models:

  • IP restrictions on API keys.
  • Withdrawal whitelist – only allow withdrawals to pre‑approved addresses.
  • Rate limit enforcement with granular user‑level statistics.

But many users do not activate these features, either because they don’t know about them or think it’s too cumbersome. The story serves as a wake‑up call.

9.4. User Responsibility

Ultimately, the user is responsible for the safety of their funds. This extends to:

  • Understanding what each API permission does.
  • Keeping private keys in secure vaults (e.g., AWS Secrets Manager, HashiCorp Vault).
  • Performing regular security audits and code reviews.

10. Moving Forward – How u/ZeroRisk Recovered

10.1. Binance’s Response

After a week of investigation, Binance determined that the withdrawal attempts were legitimate according to the API key’s permissions. They restored the user’s funds after a partial audit (approximately 80 % returned). The remaining 20 % remained in limbo, flagged as “unauthorized activity.”

The exchange also issued a public blog post highlighting API key security best practices following the incident.

10.2. Personal Reflections

u/ZeroRisk admitted on Reddit that he’d been too optimistic about his coding skills and underestimated the importance of rigorous testing:

“I think I finally understand why the community says ‘you can’t automate what you don’t understand.’ I’ll start using a proper testnet from now on.”

He pledged to open‑source his corrected bot code under an MIT license, with extensive comments about safe key handling.

10.3. The Lesson for Others

  • No “one‑click” deployment: always run the bot in a sandbox first.
  • Never commit keys to any public or private repo.
  • Test withdrawal logic carefully, ideally with a dummy address that you control and that has a low balance, before attempting full withdrawals.

11. Conclusion – The Human Element Behind the Lines of Code

The story of u/ZeroRisk is more than just a cautionary tale about missing flags or infinite loops—it’s a snapshot of how human error intersects with technology in the volatile world of cryptocurrencies. Automation offers speed and precision, but when those scripts are built without due diligence, they can become self‑fulfilling agents of loss.

From this incident we learn:

  1. Secure by Design – always apply the principle of least privilege to API keys.
  2. Test Before You Trust – leverage testnet environments rigorously.
  3. Educate Yourself – understand how your code interacts with third‑party APIs.
  4. Community Accountability – the Reddit ecosystem exemplified quick help, but also shows that collective vigilance is essential.

In a landscape where digital money flows at the speed of light and errors can ripple out in seconds, humility and rigorous security practices are not just good habits—they’re survival skills.

Let u/ZeroRisk’s accidental plunge serve as a beacon for the next wave of crypto hobbyists: build thoughtfully, test thoroughly, and always treat code like you would your own finances—carefully and with respect for its power.


Read more