admin/skills: fail loud on DB error + read skills dir from env
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 3m8s

- 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) <noreply@anthropic.com>
This commit is contained in:
2026-05-02 15:24:39 +00:00
parent 5deb38f5cf
commit f7249b7807

View File

@@ -2656,9 +2656,15 @@ async def api_reset_methodology(category: str, key: str):
PAPERCLIP_DB_URL = os.environ.get( PAPERCLIP_DB_URL = os.environ.get(
"PAPERCLIP_DB_URL", "postgresql://paperclip:paperclip@127.0.0.1:54329/paperclip" "PAPERCLIP_DB_URL", "postgresql://paperclip:paperclip@127.0.0.1:54329/paperclip"
) )
# Paperclip runs locally via pm2; skills are in ~/.paperclip # Paperclip skills directory. In the Coolify container this is bind-mounted from
_local_skills = Path.home() / ".paperclip" / "instances" / "default" / "skills" # the host's ~/.paperclip/instances/default/skills (see PAPERCLIP_SKILLS_DIR env).
PAPERCLIP_SKILLS_DIR = _local_skills # 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 # Default company ID for skills
SKILLS_COMPANY_ID = os.environ.get("PAPERCLIP_COMPANY_ID", "42a7acd0-30c5-4cbd-ac97-7424f65df294") 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") @app.get("/api/admin/skills")
async def api_list_skills(): async def api_list_skills():
"""List installed Paperclip skills with DB sync status.""" """List installed Paperclip skills with DB sync status."""
rows = []
try: try:
conn = await asyncpg.connect(PAPERCLIP_DB_URL, timeout=5) conn = await asyncpg.connect(PAPERCLIP_DB_URL, timeout=5)
try: try:
@@ -2677,9 +2682,12 @@ async def api_list_skills():
) )
finally: finally:
await conn.close() await conn.close()
except (OSError, asyncpg.PostgresError, asyncpg.InterfaceError, TimeoutError): except (OSError, asyncpg.PostgresError, asyncpg.InterfaceError, TimeoutError) as e:
# Paperclip DB unreachable — continue with disk-only skills logger.exception("Paperclip DB unreachable while listing skills")
pass raise HTTPException(
status_code=503,
detail=f"Paperclip database unreachable: {type(e).__name__}: {e}",
) from e
skills = [] skills = []
for r in rows: for r in rows: