#!/usr/bin/env python3 """Daemonize + run cece_capture_deep outside the parent process group. Cursor/agent shells often SIGKILL the whole process group when a command ends; nohup alone is not enough. Double-fork so the crawl survives. """ from __future__ import annotations import atexit import faulthandler import os import runpy import sys import time from pathlib import Path ROOT = Path("/Users/jack/Project/digital-psychology") LOG = Path("/tmp/cece-capture-deep-full.log") FAULT = Path("/tmp/cece-fault.log") PIDFILE = Path("/tmp/cece-capture-deep.pid") def log(msg: str) -> None: line = f"[{time.strftime('%F %T')}] {msg}\n" with LOG.open("a", encoding="utf-8") as f: f.write(line) def daemonize() -> None: # first fork if os.fork() > 0: sys.exit(0) os.setsid() # second fork if os.fork() > 0: sys.exit(0) # detach stdio sys.stdin.close() sys.stdout.flush() sys.stderr.flush() out = open(LOG, "a", encoding="utf-8", buffering=1) sys.stdout = out # type: ignore sys.stderr = out # type: ignore def main() -> None: os.chdir(ROOT) daemonize() PIDFILE.write_text(str(os.getpid()), encoding="utf-8") ff = FAULT.open("a", encoding="utf-8") faulthandler.enable(file=ff, all_threads=True) log(f"daemon start pid={os.getpid()} argv={sys.argv[1:]}") def _atexit() -> None: log(f"daemon atexit pid={os.getpid()}") atexit.register(_atexit) # remaining args after this script name args = sys.argv[1:] sys.argv = ["cece_capture_deep.py", *args] try: runpy.run_path(str(ROOT / "tools" / "cece_capture_deep.py"), run_name="__main__") log("daemon finished ok") except BaseException as ex: log(f"daemon error: {type(ex).__name__}: {ex}") import traceback traceback.print_exc() raise SystemExit(1) if __name__ == "__main__": main()