Auto-commit: v2.9 — Fix background task hangs (OAuth headless guard, SQLite timeouts, log touch)

- ai/setup.py: Added threading import; OAuth block now detects background/headless
  threads and skips run_local_server to prevent indefinite blocking. Logs a clear
  warning and falls back to ADC for Vertex AI. Token file only written when creds
  are not None.
- web/tasks.py: All sqlite3.connect() calls now use timeout=30, check_same_thread=False.
  OperationalError on the initial status update is caught and logged via utils.log.
  generate_book_task now touches initial_log immediately so the UI polling endpoint
  always finds an existing file even if the worker crashes on the next line.
- ai_blueprint.md: Bumped to v2.9; Section 12.D sub-items 1-3 marked ; item 13
  added to summary.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-21 10:50:00 -05:00
parent c2d6936aa5
commit 1f01fedf00
3 changed files with 47 additions and 12 deletions

View File

@@ -20,7 +20,7 @@ def db_log_callback(db_path, run_id, phase, msg):
"""Writes log entry directly to SQLite to avoid Flask Context issues in threads."""
for _ in range(5):
try:
with sqlite3.connect(db_path, timeout=5) as conn:
with sqlite3.connect(db_path, timeout=30, check_same_thread=False) as conn:
conn.execute("INSERT INTO log_entry (run_id, timestamp, phase, message) VALUES (?, ?, ?, ?)",
(run_id, datetime.utcnow(), phase, str(msg)))
break
@@ -32,7 +32,7 @@ def db_progress_callback(db_path, run_id, percent):
"""Updates run progress in SQLite."""
for _ in range(5):
try:
with sqlite3.connect(db_path, timeout=5) as conn:
with sqlite3.connect(db_path, timeout=30, check_same_thread=False) as conn:
conn.execute("UPDATE run SET progress = ? WHERE id = ?", (percent, run_id))
break
except sqlite3.OperationalError: time.sleep(0.1)
@@ -48,6 +48,15 @@ def generate_book_task(run_id, project_path, bible_path, allow_copy=True, feedba
# Log to project root initially until run folder is created by engine
initial_log = os.path.join(project_path, log_filename)
# Touch the file immediately so the UI has something to poll even if the
# worker crashes before the first utils.log() call.
try:
with open(initial_log, 'a', encoding='utf-8') as _f:
pass
except Exception:
pass
utils.set_log_file(initial_log)
# Hook up Database Logging
@@ -57,9 +66,12 @@ def generate_book_task(run_id, project_path, bible_path, allow_copy=True, feedba
# Set Status to Running
try:
with sqlite3.connect(db_path, timeout=10) as conn:
with sqlite3.connect(db_path, timeout=30, check_same_thread=False) as conn:
conn.execute("UPDATE run SET status = 'running' WHERE id = ?", (run_id,))
except: pass
except sqlite3.OperationalError as e:
utils.log("SYSTEM", f"⚠️ Database locked when setting run status (run {run_id}): {e}")
except Exception:
pass
utils.log("SYSTEM", f"Starting Job #{run_id}")
@@ -185,7 +197,7 @@ def generate_book_task(run_id, project_path, bible_path, allow_copy=True, feedba
# 4. Update Database with Final Status
try:
with sqlite3.connect(db_path, timeout=10) as conn:
with sqlite3.connect(db_path, timeout=30, check_same_thread=False) as conn:
conn.execute("UPDATE run SET status = ?, cost = ?, end_time = ?, log_file = ?, progress = 100 WHERE id = ?",
(status, total_cost, datetime.utcnow(), final_log_path, run_id))
except Exception as e: