Fix disappearing artifacts and guard against empty builds

The agent's background build threads called jsonify()/send_file() with no
Flask app context, so after a successful build they crashed with
'Working outside of application context', which triggered the error
handler that rmtree'd the whole build dir before the controller could
download the artifact. Wrap both runners in app.app_context().

Also guard all three layers against empty results: the agent refuses to
report success or serve a zip when dist/ has no files, and the scheduler
flags a downloaded empty zip as a failure rather than marking the job
done.
This commit is contained in:
Brett Williams
2026-08-31 10:07:04 -05:00
parent 1faa544ead
commit f73a5bbb2f
2 changed files with 21 additions and 4 deletions
+11 -1
View File
@@ -261,6 +261,7 @@ def _run_checkout_build_background(job_id, repo_url, repo_dir, start_time):
releases it when the work finishes."""
def runner():
try:
with app.app_context():
subprocess.check_call(['git', 'clone', repo_url, repo_dir])
install_and_build(repo_dir, job_id, start_time)
except Exception as e:
@@ -278,6 +279,7 @@ def _run_build_background(job_id, project_path, start_time):
the thread releases it when the build finishes."""
def runner():
try:
with app.app_context():
install_and_build(project_path, job_id, start_time)
except Exception as e:
print(f"Background build failed for {job_id}: {e}")
@@ -444,6 +446,13 @@ def install_and_build(project_path, job_id, start_time):
return jsonify({"error": f"Error compiling project: {e}"}), 500
dist_path = os.path.join(project_path, "dist")
if not any(os.path.isfile(os.path.join(root, f)) for root, _, files in os.walk(dist_path) for f in files):
print(f"Error: build for {job_id} reported success but {dist_path} is empty")
system_status['status'] = "ready"
system_status['running_job'] = None
_clear_progress(job_id)
return jsonify({"error": f"Build completed but produced no files in {dist_path}"}), 500
system_status['status'] = "ready"
system_status['running_job'] = None
_clear_progress(job_id)
@@ -485,7 +494,8 @@ def download_binaries(job_id):
job_path = os.path.join(TMP_DIR, BUILD_DIR, job_id)
dist_path = os.path.join(job_path, "dist")
if not os.path.exists(dist_path):
if not os.path.exists(dist_path) or not any(
os.path.isfile(os.path.join(root, f)) for root, _, files in os.walk(dist_path) for f in files):
return jsonify({"error": f"No binaries found for ID: {job_id}"}), 404
# Create a temporary zip file
+7
View File
@@ -3,6 +3,7 @@ import logging
import os
import threading
import time
import zipfile
import requests
@@ -225,6 +226,12 @@ class Scheduler:
for chunk in resp.iter_content(chunk_size=8192):
f.write(chunk)
with zipfile.ZipFile(dest) as zf:
files = [n for n in zf.namelist() if not n.endswith("/")]
if not files:
os.remove(dest)
raise RuntimeError(f"Worker {worker['url']} returned an empty artifact zip for {worker_job_id}")
db.update_job(job_id, artifacts=[fname])
# ---- logging / status -------------------------------------------------