← Blog · Behind the scenes · 10 min read · November 11, 2026

How one person and AI agents shipped a sports app in 19 languages: what worked and what broke

I was laid off from Moovit in its last round of layoffs. I decided not to look for the same job again and to work full time on BeTeam, a free sports prediction game for friend groups that I had been building on the side. There is no team. There is me, and there are AI coding agents. This is what that setup actually looks like after seven months: what works, what broke in production, and what I still have to do myself.

The numbers first

Players4,800+ people who made predictions, from 132 countries
App languages19, including two right-to-left (Hebrew and Arabic)
Sports and competitions7 sports, 11,800+ competitions
World Cup 2026146 → 4,143 registered users in 5 weeks; about 200,000 predictions typed in by real players
CodeAbout 1,980 commits since the first one on April 9, 2026; roughly 200 of them are from bots
People on payroll0

These are counted the strict way: no bots, no test accounts, and no predictions the app filled in automatically for people who forgot. I learned to count like that the hard way, which is part of the story.

How the "team" works

The agents are Claude Code sessions. Each one gets a task, its own git worktree and its own branch, and ends in a pull request. A session can start side sessions for work that should run in parallel. This post is an example: a session working on press outreach found that the outlets I wanted only link to real articles, and it started a separate session to draft this one. I rewrote it and approved every line.

What makes this work is not the model. It is the rails around it:

An agent forgets everything between sessions. A rule only survives if it lives in a file the next session reads, or better, in a check that fails.

Translating into 19 languages when you read two

I read Hebrew and English. The app has about 2,900 strings per language. The process is written down as a playbook, and its first line is the important one: no layer of checking is allowed to depend on me reading the language.

  1. A glossary before any translation. Not invented: mined from pages that already exist in that language, then locked. It also fixes style decisions: formal or informal address, how to handle gender, what stays untranslated (team and league names, the app name).
  2. Agents translate in parallel, one file each, against the glossary.
  3. Machine gates in CI. Every key exists in every language, every {{placeholder}} survives, and every plural form the language needs is present, checked against the browser's own plural rules.
  4. Blind back-translation. A separate agent gets only the translated file, never the English, and translates it back. A script compares meaning key by key and flags drift and reversals.
  5. Separate reviews for terminology, grammar and register, and for how each string is used on screen.
  6. A native speaker is a required gate in the playbook, on top of all of that.

The bugs it caught were never the ones I expected. Plurals were the number one source:

Right-to-left is its own category. In Hebrew and Arabic, a score has to show the home team on the correct side, "align right" flips when the layout flips, and team names stay in Latin letters inside Arabic text. Some of these only show up on the second cold start of the app, which is why the QA steps say "restart twice."

What broke, and what each failure left behind

1. The World Cup and the connection pool

On June 11, the World Cup opener, the app looked stuck. Sign-ups were failing with a duplicate-key error, and live games froze as "scheduled." We patched those. Three days later, at the kickoff of Germany vs Curaçao, about 1,400 leagues included that same game, and the background jobs for all of them started at the same moment. The API ran out of database connections and was unreachable for about twelve minutes. I restarted the server from my phone's browser.

It took two and a half hours to find the real cause, and the first ideas were wrong. The agent kept raising the app's own connection limit, which made it worse. The answer came from data, not guessing: the database itself had free connections, but the connection pooler in front of it allowed only 15. Raising that number to 30 fixed it within minutes. Three days later, after we added a second server region, it happened again for the same reason, and the fix was the same lever, raised to 40.

What it left behind: a written diagnosis order (check the database's own health, probe the pooler, read the pool size) and a rule that adding app servers is not the fix when the pooler is the wall. Also a rule for marketing copy: we never claim zero downtime.

2. The update that crashed on launch

On October 30 we published an over-the-air update. After publishing, a check that inspects the bundle reported that it would fall back to localhost. The agent investigated, decided it was a false alarm, and told me the update was fine. It was not. A stale build cache had produced a bundle without the database URL and the analytics key, and the app crashed on launch. The update system rolled phones back to the previous version on its own. Nobody was paged, because there is nobody to page.

What it left behind: a release preflight command that refuses to publish from an old commit, a dirty tree, a missing environment file or stale shared packages; builds that always clear the cache; the bundle check runs before publishing; and one plain rule: a failed check means roll back first. A check that exists because a failure reached production once is believed over an agent's reasoning.

3. A read-only query that stopped every write

On October 26, an analysis query ran against production through a read-only connection. It joined a large table to itself over 30 days and spilled about 4 GB of temporary files in 90 seconds. The disk crossed the host's safety threshold, the platform switched the whole database to read-only for 34 seconds, and every write in the app failed. "Read-only" protected the data. It did not protect the disk.

What it left behind: before any analytical query on a big table, run a plain EXPLAIN first, start with a narrow time window, and replace "next row" self-joins with window functions. A related rule is enforced by the hook: one session setting on the pooled connection can outlive the session and turn later, unrelated writes into errors, so the command is simply blocked.

There was a smaller one too: test sign-ups from a development build reached production, because the dev bundle silently read the production environment file. Five test users, deleted the next day. That is why tests that create accounts now run only on the local stack.

What AI could not do

What I would tell someone starting the same way

The earlier part of this story, moving the app off a no-code platform, is in From Bubble.io to React Native in 4 days. If you want to see what the agents and I built, BeTeam is free, has no real-money betting, and lets you start a private league for your group in any of its supported competitions. How points work is on the scoring page.

About the author. Ran Kopchovski is the founder of BeTeam, a free social sports prediction app for friend groups, offices and bars. He builds and runs it alone with AI coding agents. Press: press kit.

Follow BeTeam

Results, match-day moments and new competitions.