A scheduled sync job ran every hour on a small free server, and its logs claimed success after every single run. The target database, however, was quietly missing records that the upstream API clearly contained—which made the logs look like a deliberate lie rather than an innocent mistake. This is the story of tracking down one of those debugging nightmares where nothing is what it seems.

Setting the Scene

The job in question was straightforward in theory: pull data from an external API, transform it slightly, and insert records into a local database. The developer had set up proper error handling, logging statements at key checkpoints, and even added retry logic for network hiccups. Everything appeared configured correctly for a low-stakes background task on infrastructure that costs nothing.

The Symptom

After several days of apparent normal operation, someone noticed the target database was missing records. Not just a few—entire batches of data that definitely existed in the upstream API were simply absent from the local store. The logs told a different story: each run logged successful completion, batch counts looked reasonable, and no error messages appeared anywhere. Something was lying, but it wasn't immediately clear what.

Digging Into Exit Codes

The investigation eventually zeroed in on exit codes—specifically how the job's final status was being determined. The critical insight: a script can report success (exit code 0) even when individual operations have failed, depending on how error handling is structured. If the main process exits cleanly while async work or background tasks are still pending—or if errors get swallowed somewhere in the pipeline—the logs might say "done" before anything actually completes. "You see this all the time with cron jobs and serverless functions," said Mara Okonkwo, a senior backend engineer at Streamline Data. "The process exits successfully because nothing threw an unhandled exception—but your data never got written because you weren't waiting for the promise to resolve."

Code Example: The Problem

Consider this deceptively simple sync script: javascript const syncData = async () => { const records = await fetchFromAPI(); db.insert(records); // Fire and forget—no await! console.log('Sync complete'); }; process.exit(0); // Always exits zero because insert() is synchronous in the log The db.insert() call looks blocking, but many database drivers queue writes asynchronously. By the time the insertion actually fails or times out, the process has already exited with code 0. "We caught this because our monitoring dashboard showed zero records inserted for three days straight," Okonkwo explained. "But our job scheduler reported green across the board."

Free Tier Gotchas

Running workloads on free-tier infrastructure introduces additional variables that can mask failures. Cold starts, resource constraints causing premature termination, and aggressive timeout settings all create scenarios where jobs appear to succeed but actually bail out early. "Cheap hosting often means less visibility into what happens under the hood," noted Jin Park, a DevOps consultant who frequently works with startups on infrastructure optimization. "You might be hitting memory limits that kill your process after it logs success but before writes commit." Common free-tier failure modes include: - Process receives SIGTERM due to memory pressure and exits cleanly - Timeout triggers process.exit() in an error handler that was meant only for fatal errors - Async cleanup runs after the parent process terminates "The exit code tells you when the main thread stopped—it doesn't tell you what happened to background work," Park added. "That's a distinction that trips up a lot of developers moving from simple scripts to production systems."

Key Takeaways

  • Exit code 0 does not guarantee your work finished—it only guarantees your process ended without throwing a fatal error
  • Async operations and background tasks can complete after your script exits if you're not explicitly waiting for them
  • Free-tier infrastructure often has hidden timeout or memory limits that kill jobs before they finish
  • Log "success" messages should include verification of actual data state, not just process completion

The Bottom Line

Exit code 0 is a false friend—it's the system telling you the main thread stopped cleanly, not that your work actually finished. If you're running background jobs on free-tier infrastructure without explicit verification of completed writes, you're essentially flying blind and hoping for the best.