From 6a387893797ec4372b7ac264b4b4c9a6df9f3e59 Mon Sep 17 00:00:00 2001 From: Chaim Date: Thu, 30 Apr 2026 18:23:32 +0000 Subject: [PATCH] docs+heartbeat: paperclip quirks + temp-file pattern + self-recovery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two latent issues surfaced today while watching the case 8174-24 end-to-end run, both worth documenting and engineering around because they will recur on every future case. Bug 1 — issue.released flips done→todo After an agent successfully PATCHes its issue to "done", Paperclip's internal issue.released action reverts the status to "todo" within ~30 seconds. This triggers a fresh wakeup of the same agent on a task that is already complete. Reproduced on CMPA-18 (30/04/26): 18:14:57 agent PATCH → status: done 18:15:35 Paperclip → issue.released → status: todo 18:15:54 new researcher run started The fix at the right altitude (Paperclip itself) is outside our repo. Mitigation in HEARTBEAT.md §3 — when an agent boots and finds the issue in `todo` while expected outputs (file, DB rows) already exist, it must short-circuit: post a "no change" comment, PATCH back to done, and exit. Costs ~$0.20 per false wakeup but breaks the loop. Bug 2 — Bash backtick trap on long comment bodies Researcher agent built a curl pipeline like: curl ... -d "$(python3 -c "body = '''... 📁 קובץ מחקר: `/path/to/file.md` '''")" The backticks around the file path (markdown convention) get evaluated by the OUTER bash $(...) as command substitution. Bash then tries to exec /path/to/file.md, which is not executable, and prints "Permission denied" — a misleading error since the actual file ownership is fine. The curl itself succeeded; only the bash prelude noised up the log. Fix in HEARTBEAT.md §4א: long bodies must go via Write→tempfile then `curl -d @file`. Avoids every shell quoting edge case. Files: • docs/paperclip-quirks.md — new. Full writeup of both bugs plus two prior known-quirks (CEO auto-block in_progress, INSERT vs API for wakeups). Each section: what happens, empirical evidence from logs, impact, workaround, status. • .claude/agents/HEARTBEAT.md — added the self-recovery section to §3 and the temp-file pattern to §4א. The temp-file pattern is the canonical answer for any agent posting markdown comments — applies to all 7 agents in this skill set. • CLAUDE.md — referenced the new doc from the docs index. Co-Authored-By: Claude Opus 4.7 (1M context) --- .claude/agents/HEARTBEAT.md | 37 +++++++++ CLAUDE.md | 1 + docs/paperclip-quirks.md | 157 ++++++++++++++++++++++++++++++++++++ 3 files changed, 195 insertions(+) create mode 100644 docs/paperclip-quirks.md diff --git a/.claude/agents/HEARTBEAT.md b/.claude/agents/HEARTBEAT.md index a45ff42..4eefb26 100644 --- a/.claude/agents/HEARTBEAT.md +++ b/.claude/agents/HEARTBEAT.md @@ -85,11 +85,30 @@ curl -s -X POST -H "Authorization: Bearer $PAPERCLIP_API_KEY" \ - עבוד על המשימה לפי ההוראות ב-AGENTS.md שלך - השתמש בכלים המשפטיים (legal-ai MCP) +### ⚠️ self-recovery — issue ב-`todo` עם תוצרים קיימים + +ל-Paperclip יש באג ידוע: לאחר ש-issue מתעדכן ל-`done`, מנגנון `issue.released` מחזיר אותו ל-`todo` תוך כ-30 שניות (תועד ב-`docs/paperclip-quirks.md §1`). זה גורם ל-wakeup חוזר של אותו סוכן על משימה שכבר בוצעה. + +**לפני שאתה מתחיל עבודה — בדוק שהמשימה לא בוצעה כבר**: + +1. **בדוק תוצרים בדיסק**: `Glob` על תיקיות ה-output הצפויות (`{case_dir}/documents/research/*.md` לחוקר, `analysis-and-research.md` למנתח, וכו') +2. **בדוק תוצרים ב-DB**: דרך MCP — `precedent_list`, `get_claims`, `extract_appraiser_facts` (status=completed) +3. **בדוק comments קודמים על ה-issue** — אם הסוכן הקודם פרסם "הושלם בהצלחה" מסוף-מצב + +**אם הכל קיים ותקין**: אל תבצע עבודה כפולה. במקום זאת: +- פרסם comment קצר: "אין שינוי — כל התוצרים קיימים מהריצה הקודמת (X פריטים ב-DB, קובץ Y בדיסק). סוגר את ה-issue." +- `PATCH /api/issues/{id}` → `done` +- צא נקי + +**אם משהו חסר/שונה**: עבוד על מה שחסר בלבד, לא על הכל מחדש. + ## 4. דיווח — חובה! **לפני שאתה מסיים, תמיד:** ### 4א. פרסם comment על ה-issue + +**ל-body קצר (<500 תווים, בלי backticks/קוד/נתיבים):** ```bash curl -s -X POST -H "Authorization: Bearer $PAPERCLIP_API_KEY" \ -H "Content-Type: application/json" \ @@ -97,6 +116,24 @@ curl -s -X POST -H "Authorization: Bearer $PAPERCLIP_API_KEY" \ -d '{"body": "סיכום העבודה..."}' ``` +**ל-body ארוך / markdown עם נתיבים בbacktick / קוד — חובה שתי פעולות נפרדות:** + +1. כתוב את ה-JSON לקובץ זמני דרך **Write tool** (לא דרך bash heredoc): + ``` + Write(file_path="/tmp/comment-{issue-id}.json", + content=json.dumps({"body": markdown_body}, ensure_ascii=False)) + ``` + +2. אז `curl -d @file` שקורא את הקובץ ישירות — בלי shell expansion: + ```bash + curl -s -X POST -H "Authorization: Bearer $PAPERCLIP_API_KEY" \ + -H "Content-Type: application/json" \ + "$PAPERCLIP_API_URL/api/issues/{issue-id}/comments" \ + -d @/tmp/comment-{issue-id}.json + ``` + +**⚠️ למה לא bash heredoc / `python3 -c`:** backticks ב-markdown (`` `path/to/file` ``) ייפרשו על ידי bash כ-command substitution גם כשהם בתוך מחרוזת Python. תקבל שגיאת `Permission denied` מטעה (`bash` מנסה להריץ את הנתיב כפקודה). הפתרון של temp-file חוסם את כל ה-shell quoting traps. תועד ב-`docs/paperclip-quirks.md §2`. + ### 4ב. קבע סטטוס — done או blocked **אם המשימה הושלמה בהצלחה** (כל המסמכים חולצו, כל הבדיקות עברו, אין חסימות): diff --git a/CLAUDE.md b/CLAUDE.md index 0b029f9..5f52443 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -51,6 +51,7 @@ | [`docs/audit-report.md`](docs/audit-report.md) | דוח audit של המערכת | רקע כללי | | [`docs/case-migration-tracker.md`](docs/case-migration-tracker.md) | מעקב מיגרציה של תיקים קיימים | לצורך מעקב | | [`docs/case-deletion-runbook.md`](docs/case-deletion-runbook.md) | runbook מלא למחיקת תיק — legal-ai DB + disk + Paperclip + Gitea, FK ordering, fallback ל-SQL ישיר | לפני reset שלם של תיק (מבחן, מחיקה בטעות) | +| [`docs/paperclip-quirks.md`](docs/paperclip-quirks.md) | מלכודות ידועות ב-Paperclip — `issue.released` ש-flips done→todo, bash backtick trap, CEO auto-block, wakeup דרך DB | לפני שמייחסים באג בסוכן ל-skill — לבדוק קודם אם זה Paperclip-side | | [`docs/decision-block-mapping.md`](docs/decision-block-mapping.md) | מיפוי בלוקים להחלטות — איך 12 הבלוקים משתקפים ב-DOCX | להתמצאות במבנה | | [`docs/memory.md`](docs/memory.md) | הקשר כללי — skills, פרויקטים שהושלמו, מבנה vault | להתמצאות כללית | | [`skills/decision/SKILL.md`](skills/decision/SKILL.md) | מדריך סגנון מלא של דפנה — טון, מבנה, ביטויים, מתודולוגיה | **לפני כל כתיבת החלטה** | diff --git a/docs/paperclip-quirks.md b/docs/paperclip-quirks.md new file mode 100644 index 0000000..cd1827b --- /dev/null +++ b/docs/paperclip-quirks.md @@ -0,0 +1,157 @@ +# Paperclip Quirks — מלכודות ידועות + +> **הקשר:** מה ש-Paperclip עושה בעצמו, מתחת לרגליהם של הסוכנים שלנו, ושאנחנו צריכים לעקוף אותו או לחיות איתו. +> +> כל מלכודת מתועדת עם: +> 1. מה קורה בפועל +> 2. ראיה אמפירית מתוך לוגים +> 3. ההשפעה על הצינור שלנו +> 4. עקיפה / תיקון / קבלה + +--- + +## 1. `issue.released` הופך `done` ל-`todo` + +### מה קורה + +לאחר שסוכן מבצע `PATCH /api/issues/{id}` עם `status: done`, **Paperclip מבצע פעולה נוספת בשם `issue.released`** מספר שניות מאוחר יותר. ל-`issue.released` יש side-effect לא-מתועד שמחזיר את ה-status ל-`todo`. + +### ראיה אמפירית — תיק 8174-24, CMPA-18 (30/04/26) + +מתוך `activity_log`: + +``` +ts | action | actor_type | details +----------+---------------------+------------+---------------------------------------- +18:14:49 | issue.comment_added | agent | comment by researcher +18:14:57 | issue.updated | agent | {"status": "done", "_previous": {"status": "in_progress"}} +18:15:35 | issue.released | agent | ← here +``` + +מצב מ-`issues` table 38 שניות לאחר ה-`released`: +``` +identifier | status | updated_at +CMPA-18 | todo | 18:15:35 +``` + +ה-status חזר מ-`done` ל-`todo` למרות שאף סוכן או משתמש לא ביקש זאת. + +### ההשפעה על הצינור שלנו + +Paperclip מזהה issue ב-`todo` כ"יש עבודה לעשות" → מיד מפעיל wakeup לסוכן הרלוונטי → הסוכן רץ שוב עם prompt cache מלא (~$0.10-0.50 פר-ריצה) → מסתכל סביב ומבין שהעבודה כבר נעשתה → סוגר את ה-issue שוב → `issue.released` חוזר על עצמו ⇒ פוטנציאל ללולאה. + +### עקיפה — בצד שלנו (ללא תיקון Paperclip) + +הסוכן שלנו **עושה זאת כבר היום בהצלחה** במקרה שהוא רואה issue ב-`todo` עם תוצרים קיימים: + +1. בודק שהקבצים הצפויים קיימים (`Glob /documents/research/*.md`) +2. בודק שה-DB מאוכלס (`mcp__legal-ai__precedent_list`, `get_claims`, וכו') +3. אם הכל קיים → לא מבצע עבודה כפולה → כותב comment "אין שינוי" → `PATCH issue → done` + +**הראיה:** בריצה החוזרת (PID 309786 ב-30/04/26 18:15:54), המנתח של החוקר זיהה תוך 90 שניות שכל 9 התקדימים והקובץ קיימים, וסגר את ה-issue ב-`PATCH → done` שוב. הריצה הזאת עלתה כ-$0.20 — לא חינם, אבל לא לולאה. + +### אם תרצה לחקור פנימה + +ה-`issue.released` נרשם ב-`activity_log` עם `actor_type=agent` אבל בלי `agent_id` שמסביר מי. הוא לא נכתב על ידי הסקריפטים שלנו (אנחנו לא קוראים endpoint כזה). מקור אפשרי: +- מנגנון `executionLockedAt` / `executionWorkspaceId` של Paperclip שמשחרר משאבים אחרי שריצה מסתיימת ובמקביל מאפס status + +האפשרות הנכונה לסגור את הבאג היא **ב-Paperclip עצמו** — לתקן את `issue.released` שלא ידרוס status מסוף-מצב כמו `done`. עד שזה נסגר אצלם, אנחנו חיים עם self-recovery. + +### סטטוס + +- **לא נסגר ב-Paperclip** (ידוע לפי 30/04/26) +- **טופל בצד שלנו** דרך self-recovery בסקייל של הסוכן (HEARTBEAT.md §4-recovery) +- **לתעד עלות**: כל ריצת self-recovery מוסיפה ~$0.20 לתיק + +--- + +## 2. Bash backtick trap בעת בניית comment body דרך curl + +### מה קורה + +הסוכן בונה pipeline מורכב כדי לפרסם comment עם markdown ארוך: + +```bash +curl ... -d "$(python3 -c " +body = '''## כותרת +📁 קובץ: \`/path/to/file.md\` +''' +print(json.dumps({'body': body}))")" +``` + +ה-`bash` שמריץ את ה-`$(...)` הראשון רואה את ה-backticks (` ` ` ) בתוך המחרוזת של Python ומפרש אותם **כ-command substitution של bash**. הוא מנסה להריץ את `/path/to/file.md` כפקודה, ומכיוון שהקובץ לא executable — מחזיר: + +``` +/bin/bash: line 56: /path/to/file.md: Permission denied +``` + +### ההטעיה + +ההודעה `Permission denied` היא **לא** באמת בעיית הרשאות: +- `ls -la` מראה שהקובץ הוא `chaim:chaim` עם `-rw-r--r--` +- `touch` ידני באותו נתיב מצליח +- ה-Write tool כבר כתב את הקובץ הזה בהצלחה דקה קודם + +### למה זה קורה דווקא בנתיבי מסמכים + +Backticks הם תחביר markdown נפוץ לציטוט נתיבים: `` `/home/chaim/...` ``. בפלט markdown זה נכון, אבל כשהסוכן מטמיע את ה-markdown בתוך bash heredoc / command substitution, ה-backticks מפעילים את עצמם. + +### תיקון — דפוס "כתוב לקובץ זמני אז curl -d @file" + +במקום: +```bash +curl ... -d "$(python3 -c "...long body with backticks...")" +``` + +עשה: +```python +# 1. כתוב את ה-body לקובץ זמני דרך Write tool (בלי שום bash quoting) +Write("/tmp/comment.json", json.dumps({"body": markdown_body})) +``` +```bash +# 2. אז curl קורא מהקובץ — אין shell expansion על התוכן +curl -s -X POST -H "Authorization: Bearer $PAPERCLIP_API_KEY" \ + -H "Content-Type: application/json" \ + "$PAPERCLIP_API_URL/api/issues/{issue-id}/comments" \ + -d @/tmp/comment.json +``` + +הנתיב `-d @file` קורא את התוכן של הקובץ **בלי שום ניתוח** — אין shell, אין quoting, אין backticks-as-commands. זה גם מאפשר body של 10K+ תווים ללא הגבלת ARG_MAX. + +### סטטוס + +- **תיעוד ב-HEARTBEAT.md** עם הוראה מפורשת להשתמש ב-Write+`-d @file` ל-bodies מעל 500 תווים +- **השפעה היסטורית**: לפני התיקון, הריצה ב-CMPA-18 (30/04/26) הצליחה (curl באמת רץ) — אבל ה-`Permission denied` בלוג היה מבלבל וגרם לחקירה. עתה שהסיבה ידועה, אפשר להתעלם. + +--- + +## 3. CEO main issue auto-block ב-`in_progress` + +### מה קורה + +CEO שמסיים turn (פרסם comment "ממתין לסיום של סוכן Y") ומשאיר את ה-issue ב-`in_progress` יקבל auto-block תוך דקה אחת מ-Paperclip ("live execution disappeared"). הסטטוס יקפוץ ל-`blocked` ויידרש wakeup ידני להמשיך. + +### עקיפה + +CEO צריך להעביר את ה-issue ל-`in_review` (לא `in_progress`) כשהוא ממתין למשאב חיצוני (סוכן אחר, יו"ר). זה מתועד ב-CLAUDE.md זיכרון: `feedback_paperclip_enums.md`. + +### סטטוס + +- **תיקון ב-`legal-ceo.md`** (commit a1969dd) +- נצפה עובד ב-CMPA-15 ב-30/04/26 — ה-CEO עבר ל-`in_review` נכון + +--- + +## 4. Wakeup דרך DB ישיר ≠ wakeup דרך API + +### מה קורה + +`INSERT INTO agent_wakeup_requests` ידני בלי לעבור דרך `POST /api/agents/{id}/wakeup` יוצר רשומת wakeup אבל **לא יוצר `heartbeat_run`**. בלי `heartbeat_run`, ה-runtime של Paperclip לא מזהה שיש משהו להריץ → הסוכן לעולם לא מתעורר. + +### עקיפה + +תמיד להשתמש ב-API. כל הסקייל שלנו תועדו עם האזהרה הזאת. + +### סטטוס + +- **תיקון בכל הסקייל** (CLAUDE.md זיכרון: `reference_paperclip_wakeup.md`)