Install
openclaw skills install llm-safe-writeSafely write large files or files with CJK/special characters using incremental Edit strategy instead of direct Write. Use when: writing a file longer than 50 lines, file contains CJK characters (Chinese/Japanese/Korean), Write tool failed with "Unterminated string" or JSON truncation, writing Python/JS/TS files with multiple functions, embedding CJK strings in source code, user says "write failed", "file won't write", "large file write", "CJK write error", "encoding error on write", or "file too long to write". **Do NOT undertrigger** — when a file may exceed 50 lines or contain non-ASCII characters, use this Skill proactively instead of bare Write.
openclaw skills install llm-safe-writeUse this skill when:
Unterminated string or JSON truncation errorsDo NOT use this skill when:
The Write tool serializes file content as a JSON string for transport. CJK characters occupy 3-4 bytes each in UTF-8. When a multi-byte character happens to cross an internal buffer boundary, the JSON string gets truncated, causing Unterminated string errors. ASCII-only content avoids this because each character is exactly 1 byte.
Key facts:
Scope clarification: The Read and Edit tools are not affected by this issue — Read only reads (never writes), and Edit's
oldString/newStringare short strings (< 30 lines each) that never trigger truncation. Only the Write tool has this limitation.
Evaluate the file first: if any condition below is met, go directly to skeleton + Edit — do NOT try Write first:
- File > 50 lines → skeleton + Edit
- Content contains CJK / special characters → ASCII skeleton + Edit to fill CJK
- Write already failed → switch immediately, never retry
Only for files that are short (< 50 lines) and pure ASCII may you try Write directly — but if it fails, switch immediately.
Never retry a failed Write with the same content — this is the hard constraint of this Skill.
| Scenario | Strategy |
|---|---|
| < 50 lines + pure ASCII | You may try Write directly; for files > 50 lines or with CJK, go directly to skeleton + Edit |
| > 50 lines / contains CJK / Write failed | Skeleton + Edit |
| Small edit to existing file (< 30 lines change) | Edit tool directly |
| Large rewrite of existing file / CJK content | Read file → Edit in small chunks with unique anchors |
When unsure, try the simpler approach first and fall back to skeleton+Edit if it fails. Never retry a failed Write.
Trigger condition: Creating a new file expected to be > 50 lines, or containing CJK content.
Steps:
PLACEHOLDER_xxxExample — writing a 200-line Python file:
Step 1 — Write the skeleton:
import argparse
import base64
import json
from paddleocr import PaddleOCR
from openai import OpenAI
API_BASE = "PLACEHOLDER_API_BASE"
API_KEY = "PLACEHOLDER_API_KEY"
PROMPT = "PLACEHOLDER_PROMPT"
def get_ocr():
pass # PLACEHOLDER_get_ocr
def detect_texts(image_path):
pass # PLACEHOLDER_detect_texts
def extract_json(text):
pass # PLACEHOLDER_extract_json
def main():
pass # PLACEHOLDER_main
if __name__ == "__main__":
main()
Step 2 — Fill each function with Edit (one at a time):
Edit(filePath="/path/to/file.py",
oldString="pass # PLACEHOLDER_get_ocr",
newString="ocr = PaddleOCR(use_angle_cls=True, lang='ch')\n return ocr")
Edit(filePath="/path/to/file.py",
oldString="pass # PLACEHOLDER_detect_texts",
newString="result = ocr.ocr(image_path, cls=True)\n return result")
Trigger condition: Modifying an existing file that is already large or contains CJK.
Trigger condition: Need to add content at the end of an existing file.
The Edit tool can only replace, not append. To add content at the end of a file:
oldString, then repeat it in newString after the new content.if __name__ == "__main__":, use that line as the anchor and replace it with new_function_code\n\nif __name__ == "__main__":.# END_OF_FILE) via Edit, use it as anchor for subsequent appends, then remove it with a final Edit. ⚠️ Prefer stable anchors (like if __name__ == "__main__":) whenever available — if the agent is interrupted before the final cleanup Edit, the marker will remain in the file as residue.Trigger condition: File content includes Chinese, Japanese, Korean, or other non-ASCII characters.
"\u8bc6\u522b" instead of "识别") — this keeps the file ASCII-safe but less readable.Example — Python file with Chinese prompts:
Step 1 — Skeleton with placeholders:
PROMPT_SYSTEM = "PLACEHOLDER_prompt_system"
PROMPT_USER = "PLACEHOLDER_prompt_user"
ERROR_MSG = "PLACEHOLDER_error_msg"
Step 2 — Fill with Edit:
Edit(filePath="/path/to/file.py",
oldString='PROMPT_SYSTEM = "PLACEHOLDER_prompt_system"',
newString='PROMPT_SYSTEM = "你是一个专业的AI助手,请用中文回答问题"')
Trigger condition: Writing config/data files that lack code-style comment placeholders.
These files lack code-style comment placeholders. Strategy:
0, false, null) rather than a string — a string placeholder like "PLACEHOLDER" would break JSON type validity. Edit the field afterward with a short Edit call to replace the placeholder with the real value, or use a two-step Edit: first replace with a string, then fix the type.# PLACEHOLDER_key_name comments as anchors on the line above the value to replace.A good skeleton should include:
pass as multiple pass statements cause Edit tool Found multiple matches errors.main() or entry point at the bottomFor classes, use # PLACEHOLDER_ClassName_method_name format to ensure uniqueness across methods:
class MyHandler:
def __init__(self):
pass # PLACEHOLDER_MyHandler_init
def process(self, data):
pass # PLACEHOLDER_MyHandler_process
For JS/TS, use // PLACEHOLDER_funcName comments instead.
If an Edit call fails with oldString not found:
If an Edit call fails with Found multiple matches:
replaceAll: true if you genuinely want to replace every occurrence (e.g., renaming a variable)| Error | Cause | Fix |
|---|---|---|
Unterminated string | Write content contains CJK or is too long; JSON truncated | Switch to skeleton + Edit immediately; do not retry Write |
oldString not found | File content doesn't match oldString (stale after prior edit, whitespace differences) | Re-Read the file; construct oldString from actual content |
Found multiple matches | oldString appears multiple times (e.g., bare pass) | Add more surrounding context to make oldString unique; or use replaceAll: true |
File not found | Parent directory doesn't exist for new file | Create directory first with mkdir -p, then Write |
| Write succeeds but Read shows incomplete content | JSON truncation at end of file — Write didn't error but content was cut off | Use Edit to fill in missing parts, or rewrite with skeleton + Edit |
| PowerShell corrupts multi-line code | PowerShell interprets quotes/backticks/curly braces in strings | Write code to a .py/.js file first, then execute the file |
pass # PLACEHOLDER_func_name) — bare pass triggers Found multiple matches on every subsequent Edit.oldString not found.python -c "..." with multi-line code in PowerShell → same string handling issues; code gets mangled before executionAfter completing all file writes/edits:
file.py (120 lines, verified)" — no more detail needed