Files
legal-ai/docs/paperclip-quirks.md
Chaim 6a38789379
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 7s
docs+heartbeat: paperclip quirks + temp-file pattern + self-recovery
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) <noreply@anthropic.com>
2026-04-30 18:23:32 +00:00

7.6 KiB
Raw Blame History

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 ארוך:

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"

במקום:

curl ... -d "$(python3 -c "...long body with backticks...")"

עשה:

# 1. כתוב את ה-body לקובץ זמני דרך Write tool (בלי שום bash quoting)
Write("/tmp/comment.json", json.dumps({"body": markdown_body}))
# 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)