Last Tuesday, I built a bot. It reads my project's git log every night. It writes a plain-English summary of the day's changes. I hosted it on MonkeyCode's free server with the free token tier.
Three days later, I had an error-log folder and zero trust in "free" infrastructure.
This is the debugging log. Seven failures. Seven fixes. Each one taught me something about free AI infrastructure.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
Free tier numbers change. Verify current terms in the dashboard before you depend on them.
The Setup
The bot is simple. A cron job runs at 23:00. It collects the last 20 commits. It sends them to the model. It posts the summary to a Slack webhook.
# summarize.py
import subprocess
import os
from openai import OpenAI
client = OpenAI(
base_url=os.getenv("LLM_BASE_URL"),
api_key=os.getenv("LLM_API_KEY"),
)
log = subprocess.run(
["git", "log", "--oneline", "-20"],
capture_output=True, text=True
).stdout
response = client.chat.completions.create(
model=os.getenv("LLM_MODEL"),
messages=[
{"role": "system", "content": "Summarize these commits for a non-technical stakeholder."},
{"role": "user", "content": log},
],
)
print(response.choices[0].message.content)
Simple. Naive. Wrong in seven ways.
Failure 1: The Read Timeout
Symptom: The first run died after 60 seconds.
requests.exceptions.ReadTimeout: HTTPSConnectionPool(host='api...', port=443): Read timed out.
Diagnosis: The model took longer than the default 60-second timeout. Free-tier servers are not fast. The git log was long. The model had a lot to read.
Fix: Raise the timeout. Add retries with exponential backoff.
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=30))
def call_model(messages):
return client.chat.completions.create(
model=os.getenv("LLM_MODEL"),
messages=messages,
timeout=120,
)
Verify: Run the script. Watch it survive past 60 seconds.
Failure 2: Markdown-Wrapped JSON
Symptom: The bot printed a JSON object wrapped in a code fence.
```json
{"summary": "..."}
```
Diagnosis: The model decided to be helpful and add syntax highlighting. My parser expected raw JSON.
Fix: Strip code fences before parsing.
import re
import json
def extract_json(text):
match = re.search(r"```
(?:json)?\s*(.*?)\s*
```", text, re.DOTALL)
if match:
return json.loads(match.group(1))
return json.loads(text)
Verify: Feed it a fenced response. Confirm it parses.
Failure 3: The 429 Wall
Symptom: Day two. The bot stopped.
openai.RateLimitError: Error code: 429 - quota exceeded
Diagnosis: I burned the free token quota in one night. The git log had 20 commits with long messages. Each call consumed thousands of tokens. I never checked the usage dashboard.
Fix: Truncate the input. Count tokens before sending.
log = subprocess.run(
["git", "log", "-5", "--format=%h %s"],
capture_output=True, text=True
).stdout[:2000]
Five commits. Two thousand characters. Enough for a useful summary.
Verify: Check the usage dashboard the next morning. Confirm the drop.
Failure 4: The OOM Kill
Symptom: The process vanished. No error in the log. Just a Killed line.
Diagnosis: The free server ran out of memory. The OOM killer targeted my Python process.
Fix: Limit memory. Keep the bot stateless.
ulimit -v 512000 # 512 MB virtual memory
Read from disk. Write to disk. Do not hold data in memory.
Verify: Run dmesg | grep -i oom. Confirm no new kills.
Failure 5: The Timezone Trap
Symptom: The summary said "yesterday" when it meant "today".
TypeError: can't compare offset-naive and offset-aware datetimes
Diagnosis: Git timestamps are timezone-naive. Python's datetime.now() on the server was timezone-aware. Comparing them crashed the script.
Fix: Force UTC everywhere.
from datetime import datetime, timezone
now = datetime.now(timezone.utc)
Verify: Run the bot at 23:59. Confirm the date rolls over correctly.
Failure 6: The Cron Env Void
Symptom: Cron ran the job. The script failed. No output.
Diagnosis: Cron does not load your shell profile. The API key was not set.
Fix: Export variables inside the cron command.
0 23 * * * cd /home/user/bot && LLM_API_KEY=... LLM_BASE_URL=... LLM_MODEL=... /home/user/bot/venv/bin/python summarize.py >> bot.log 2>&1
Verify: Check the log file. Confirm output appears.
Failure 7: Context Length Exceeded
Symptom: A 400 error.
openai.BadRequestError: Error code: 400 - context_length_exceeded
Diagnosis: One commit message was enormous. A merge commit with a 3,000-word description. It pushed the total over the model's limit.
Fix: Cap each commit message.
commits = [c[:200] for c in commits]
Verify: Send the longest commit. Confirm it fits.
What I Learned
Free infrastructure is not bad. It is just honest. It shows you every assumption you made about reliability.
- Timeouts are real. Retry.
- Models add formatting. Strip it.
- Quotas run out. Count tokens.
- Memory is limited. Keep it small.
- Timezones lie. Use UTC.
- Cron has no env. Set it explicitly.
- Context has a ceiling. Trim early.
Who Should Not Do This
Do not run this for production. Do not send sensitive commit messages to a free model. Do not expect a free server to survive a traffic spike.
Use this setup for personal projects. Use it to learn. Use it to build a habit of observability.
Try It
Build a small bot. Break it on purpose. Read every error. Fix each one.
MonkeyCode's free tier is a good place to start. The dashboard shows current limits. The same code works with any OpenAI-compatible provider.
Start with one failure. Let the error log teach you the rest.
Top comments (0)