Request the diagnostic

OOM and Auto-Restart Loops — Reading the Logs Your Platform Hides

bringforth · Rohit Chaudhri · Blog · July 3, 2026 · 7 min read

Here's why that happens, and why the evidence you need has been sitting in a log file your platform tucks one click out of sight.

Asha's app ran perfectly in rehearsal. Every screen, every data load, every transition — clean. Then, twenty minutes into the actual investor demo, it went quiet. Not a crash page, not a spinner. The app just paused, came back, and behaved as if nothing had happened. Then it did it again. Then a third time. The platform dashboard held a cheerful green "running" status the entire time. Here's why that happens, and why the evidence you need has been sitting in a log file your platform tucks one click out of sight.

Why does my app keep crashing and restarting on its own?

Your app is not crashing in the way a car stalls — it is being killed and revived in a loop. When a running process consumes more RAM than the host permits, the host terminates it instantly and the platform restarts it automatically. From the outside, the app looks alive. From the inside, it is degrading with every cycle.

The sequence is mechanical: your app starts, it builds up in-memory state, it crosses its RAM ceiling, the host's memory enforcer kills the process, the platform auto-restarts it, and the whole cycle begins again. That is an out-of-memory restart loop — OOM for short, from the operating system term "out of memory."

AI-generated MVPs are especially prone to this. Vibe-coded apps are built for speed and demonstrability, not for explicit resource ceilings. A senior engineer configuring a production service sets connection-pool limits, caps background-job concurrency, and ties memory-hungry operations to explicit thresholds. A vibe-coded app typically does none of that — the generated code reaches for data, loads it, and holds it, with no instruction to stop.

The platform auto-restart is not a safety net. It is a treadmill. Each new instance starts fresh, accumulates the same memory load, and gets killed at roughly the same point. During Asha's demo, the app was probably completing three full restart cycles while her dashboard showed green. The platform was telling the truth about uptime — it was hiding the reason why uptime was being maintained.

Where are the logs — and how do I actually open them?

The runtime log stream is one or two clicks from your app's main dashboard on every major low-code and PaaS host. On Heroku, open your app, select the "More" menu in the top-right corner, and choose "View logs." On Render, open your service and select the "Logs" tab directly from the service page. On Railway, open your deployment and click the "Deploy Logs" panel on the right side. On Fly.io, the log viewer is under "Monitoring" in the left sidebar — no terminal required.

You are not here to read every line. Log streams are long and most of what is in them is noise — routine request entries, health-check pings, scheduled tasks completing normally. Your only job is to scroll back to the timestamp just before each restart and look for one of three specific patterns. Those patterns are covered in the next section. Everything else you can ignore.

One practical note: most free and entry-level paid tiers on these platforms retain between 24 and 72 hours of log history before older entries rotate out. If your app crashed during a demo two days ago, the evidence may still be there — but it will not be there indefinitely. Open the log now, even before you know exactly what you are looking for.

What do those three scary lines in the log actually mean?

Three signals tell you the restart was caused by memory exhaustion, not broken logic in your code. Each one is readable without an engineering background. Each one points to the same root cause.

"OOM killed" or "Killed process [number]" — The host's memory enforcer reached its limit and terminated your process. The word "Killed" in a log line almost always means the operating system, not your application, ended the process. Your code did not throw an error. The host pulled the plug.

Exit code 137 — When a process is terminated by the operating system's forced-kill signal, it exits with code 137. You may see this as exit status 137, exited with code 137, or similar phrasing depending on the platform. Code 137 is not a code error — it is an OS-level signal that memory limits were crossed. If you see 137, the process did not decide to stop. It was stopped.

"Back-off restarting failed container" — This line, or a close variant, appears when the platform has restarted the same process multiple times in rapid succession and is now introducing a delay before trying again. The platform is throttling itself. By the time you see this message, the restart loop has already run several cycles. The platform is not confused — it has detected the pattern and is slowing down to avoid thrashing.

Any one of these three signals, appearing in the log at the moment before a restart, confirms memory exhaustion as the cause. You do not need to understand the rest of the log entry. The signal is the diagnosis.

What can I do right now to stop the loop — and what needs a specialist?

Two stabilizing moves require no code changes. First, go to your host's dashboard and raise the memory tier by one step — most platforms offer this as a single dropdown or plan selection. A larger memory ceiling gives the app room to complete its work before hitting the limit. This is not a fix. It is a pause. Second, cross-reference the restart timestamps with your app's activity log or any route-level logging your platform exposes. If the same screen, the same background job, or the same data-export action consistently appears in the minutes before each crash, you have identified the trigger.

Those two moves together — more headroom, narrowed trigger — can stabilize a demo environment for days or weeks.

The clear line is this: if the loop returns after the tier bump, or if the same route keeps appearing before every crash, the root cause is almost certainly an uncontrolled memory leak or unbounded data load inside the AI-generated code itself. Raising the tier again will not resolve it. Another tier bump just delays the next crash at a higher ceiling.

That condition — a memory leak or unbounded load baked into the generated code — requires a structured audit and targeted refactor. The code needs to be read, the memory-hungry paths identified, and explicit limits placed around them. That is engineering work, and it is the kind of work a senior engineer does once with the log evidence in hand, rather than indefinitely triaging symptoms.

What should I do in the next 48 hours?

Export and save the log segment containing the OOM signals before the platform rotates it.

Most free and entry-level tiers keep only 24 to 72 hours of log history. If the crash happened recently, the evidence is still there. Copy the relevant lines — the OOM signal, the exit code, the back-off message, and the timestamps around them — into a document you control. A plain text file is fine. A shared doc is fine. The format does not matter. What matters is that you have it before the platform discards it.

That saved log segment is not just a record. It is the starting artifact for any specialist engagement that follows. A vague complaint about crashes — "the app keeps going down before demos" — opens a long diagnostic conversation. A saved log showing three occurrences of exit code 137, each preceded by the same background job timestamp, shortens that conversation to minutes. The engineer or audit service you bring in can read the evidence directly instead of reconstructing it.

One more step: raise the memory tier now, before your next demo or pilot meeting. Not because it solves the underlying problem, but because it buys the time to address it properly — with the log in hand, and with the right help already engaged.

Three days. One saved log file. That is the distance between a crash that keeps happening and a crash that gets fixed.