From f7249b7807e238e68e99d237fb501fb2ddf6f623 Mon Sep 17 00:00:00 2001 From: Chaim Date: Sat, 2 May 2026 15:24:39 +0000 Subject: [PATCH] admin/skills: fail loud on DB error + read skills dir from env - Raise HTTPException(503) when Paperclip DB is unreachable instead of silently falling through to disk-only mode and returning []. - Honor PAPERCLIP_SKILLS_DIR env var (falls back to ~/.paperclip/...). In the Coolify container the host's skills dir is bind-mounted at /paperclip-skills; without this, Path.home() resolved to /root/ and the disk inventory was always empty. Both bugs together silently turned a Paperclip DB outage into "no skills installed" on the /skills page. Co-Authored-By: Claude Opus 4.7 (1M context) --- web/app.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/web/app.py b/web/app.py index 3ef78b0..8417db7 100644 --- a/web/app.py +++ b/web/app.py @@ -2656,9 +2656,15 @@ async def api_reset_methodology(category: str, key: str): PAPERCLIP_DB_URL = os.environ.get( "PAPERCLIP_DB_URL", "postgresql://paperclip:paperclip@127.0.0.1:54329/paperclip" ) -# Paperclip runs locally via pm2; skills are in ~/.paperclip -_local_skills = Path.home() / ".paperclip" / "instances" / "default" / "skills" -PAPERCLIP_SKILLS_DIR = _local_skills +# Paperclip skills directory. In the Coolify container this is bind-mounted from +# the host's ~/.paperclip/instances/default/skills (see PAPERCLIP_SKILLS_DIR env). +# Fallback to the host path for local/dev use. +PAPERCLIP_SKILLS_DIR = Path( + os.environ.get( + "PAPERCLIP_SKILLS_DIR", + str(Path.home() / ".paperclip" / "instances" / "default" / "skills"), + ) +) # Default company ID for skills SKILLS_COMPANY_ID = os.environ.get("PAPERCLIP_COMPANY_ID", "42a7acd0-30c5-4cbd-ac97-7424f65df294") @@ -2666,7 +2672,6 @@ SKILLS_COMPANY_ID = os.environ.get("PAPERCLIP_COMPANY_ID", "42a7acd0-30c5-4cbd-a @app.get("/api/admin/skills") async def api_list_skills(): """List installed Paperclip skills with DB sync status.""" - rows = [] try: conn = await asyncpg.connect(PAPERCLIP_DB_URL, timeout=5) try: @@ -2677,9 +2682,12 @@ async def api_list_skills(): ) finally: await conn.close() - except (OSError, asyncpg.PostgresError, asyncpg.InterfaceError, TimeoutError): - # Paperclip DB unreachable — continue with disk-only skills - pass + except (OSError, asyncpg.PostgresError, asyncpg.InterfaceError, TimeoutError) as e: + logger.exception("Paperclip DB unreachable while listing skills") + raise HTTPException( + status_code=503, + detail=f"Paperclip database unreachable: {type(e).__name__}: {e}", + ) from e skills = [] for r in rows: