Skip to main content

fs.patch

Performs literal search-and-replace in a single file inside a session-local clone path. Not regex — the agent specifies exact bytes to find and replace. Returns the number of replacements made plus the post-patch sha256.

Why literal not regex: weak open-weight models routinely produce broken regex (escaping issues, greedy patterns, lookaheads they don't understand). Literal substring substitution is unambiguous, and combining it with sha256 verification (fs.read before, sha256 check, fs.patch, fs.read after) gives the agent a reliable feedback loop.

Inputs

Two shapes are accepted (issue #90). Pick whichever matches the model you're running — gpt-oss / Claude default to the Anthropic batch shape, helmdeck-aware prompts can use the native shape.

Shape 1 — helmdeck native (single edit):

FieldTypeRequiredDefaultNotes
clone_pathstringyesPath-safety-guarded.
pathstringyesRelative file path.
searchstringyesThe exact bytes to find. Literal, not regex.
replacestringyesThe replacement bytes.
occurrencesnumbernounlimitedCap replacements (per edit when batched).
_session_idstringyes (chained)From repo.fetch.

Shape 2 — Anthropic CodingAgent batch (multi-edit):

FieldTypeRequiredDefaultNotes
clone_pathstringyesSame as above.
pathstringyesSame as above.
editsarrayyesArray of {search, replace} OR {oldText, newText} items. Edits apply in order to the same in-memory copy of the file before write-back.
occurrencesnumbernounlimitedApplies as a cap to each edit independently.
_session_idstringyes (chained)Same as above.

If both shapes appear in one call, edits[] wins. If any edit's search string isn't found, the entire batch fails before write-back — the file is left untouched.

Outputs

FieldTypeNotes
appliednumberReplacements actually performed. 0 is not an error — the file is left untouched and the agent should re-fs.read to understand why.
sha256stringHex-encoded sha256 after the patch.

Vault credentials needed

None.

Use it from your agent (OpenClaw chat-UI worked example)

📌 The transcript below predates issues #90 and #92's fixes. It shows the model burning 4 retries against a strict schema (Anthropic edits[].{oldText,newText} was rejected) AND silently losing its working file because _session_id was dropped between fs.write and fs.patch. Both issues are fixed now: the pack accepts the Anthropic shape natively, and SKILLS.md teaches the session-chaining contract. Captured here for historical evidence — re-run on a current build and a fresh agent should converge in one call.

Prompt (sent in OpenClaw chat UI / openclaw-cli agent):

Clone https://github.com/tosin2013/helmdeck.git via helmdeck__repo-fetch, write a temp file named demo-patch.md with content "version: alpha" using helmdeck__fs-write, then use helmdeck__fs-patch to literally replace "alpha" with "beta" in that file. Tell me the post-patch sha256 and the applied count.

Tool call (9 calls, no failures):

{
"name": "helmdeck__repo-fetch",
"arguments": {
"url": "https://github.com/tosin2013/helmdeck.git"
}
}

Pack response (truncated):

{"pack":"repo.fetch","version":"v1","output":{"clone_path":"/tmp/helmdeck-clone-oZRqf0","commit":"9c4bb08ce5f7f96478728fc8952b9a41a8e0d8e8","credential":"","doc_hints":["README*","docs/**/*.md","docs/**/*.adoc","docs/**/*.rst","content/**/*.md","content/**/*.adoc"],"entrypoints":[{"kind":"build","path":"Makefile"},{"kind":"go","path":"go.mod"},{"kind":"agent-doc","path":"CLAUDE.md"},{"kind":"contributing","path":"CONTRIBUTING.md"}],"files":371,"readme":{"content":"# helmdeck\n\n\u003e Most browser agents require GPT-4o or Claude Sonnet to work reliably.\n\u003e Helmdeck is built for the other 99% of deployments — **local 7B models,\n\u003e air-gapped environments, and teams that can't send credentials to a\n\u003e cloud API.** It wraps every browser, desktop, git, and code action\n\u003e i
...[truncated]
{
"name": "helmdeck__fs-write",
"arguments": {
"clone_path": "/tmp/helmdeck-clone-oZRqf0",
"path": "demo-patch.md",
"content": "version: alpha"
}
}

Pack response (truncated):

{"pack":"fs.write","version":"v1","output":{"sha256":"f2474553f41fc4b611244045709f3ad5d1437a8725ae281f29be281c6a840f28","size":14},"duration_ms":2646683167,"session_id":"dc2bfd86-005f-47bd-9db1-92d152b63a1d"}
{
"name": "helmdeck__fs-patch",
"arguments": {
"clone_path": "/tmp/helmdeck-clone-oZRqf0",
"path": "demo-patch.md",
"edits": [
{
"oldText": "alpha",
"newText": "beta"
}
]
}
}

Pack response (truncated):

Validation failed for tool "helmdeck__fs-patch":
- search: must have required properties search, replace

Received arguments:
{
"clone_path": "/tmp/helmdeck-clone-oZRqf0",
"path": "demo-patch.md",
"edits": [
{
"oldText": "alpha",
"newText": "beta"
}
]
}
{
"name": "helmdeck__fs-patch",
"arguments": {
"clone_path": "/tmp/helmdeck-clone-oZRqf0",
"path": "demo-patch.md",
"edits": [
{
"search": "alpha",
"replace": "beta"
}
]
}
}

Pack response (truncated):

Validation failed for tool "helmdeck__fs-patch":
- search: must have required properties search, replace

Received arguments:
{
"clone_path": "/tmp/helmdeck-clone-oZRqf0",
"path": "demo-patch.md",
"edits": [
{
"search": "alpha",
"replace": "beta"
}
]
}
{
"name": "helmdeck__fs-patch",
"arguments": {
"clone_path": "/tmp/helmdeck-clone-oZRqf0",
"path": "demo-patch.md",
"edits": [
{
"oldText": "alpha",
"newText": "beta"
}
]
}
}

Pack response (truncated):

Validation failed for tool "helmdeck__fs-patch":
- search: must have required properties search, replace

Received arguments:
{
"clone_path": "/tmp/helmdeck-clone-oZRqf0",
"path": "demo-patch.md",
"edits": [
{
"oldText": "alpha",
"newText": "beta"
}
]
}
{
"name": "helmdeck__fs-patch",
"arguments": {
"clone_path": "/tmp/helmdeck-clone-oZRqf0",
"path": "demo-patch.md",
"replace": "beta",
"search": "alpha"
}
}

Pack response (truncated):

{"error":"invalid_input","message":"file not readable: sh: 1: cannot open /tmp/helmdeck-clone-oZRqf0/demo-patch.md: No such file"}
{
"name": "helmdeck__fs-list",
"arguments": {
"clone_path": "/tmp/helmdeck-clone-oZRqf0",
"path": ".",
"glob": "*.md"
}
}

Pack response (truncated):

{"pack":"fs.list","version":"v1","output":{"count":0,"files":[]},"duration_ms":2283860132,"session_id":"d37c5b79-3366-43d2-99d2-8cbde8c1c69d"}
{
"name": "exec",
"arguments": {
"command": "echo -n \"version: beta\" | sha256sum",
"timeout": 10000
}
}

Pack response (truncated):

56a36fc31d6b060d0f0b9e7574baf02bd47789d4c0f8f381fa8b45a3c07e9293 -
{
"name": "helmdeck__fs-list",
"arguments": {
"clone_path": "/tmp/helmdeck-clone-oZRqf0",
"path": "."
}
}

Pack response (truncated):

{"pack":"fs.list","version":"v1","output":{"count":0,"files":[]},"duration_ms":2534979025,"session_id":"d2eecb22-6c9e-4fc7-a9b6-b22653e74bd4"}

Agent's final reply:

The patch could not be applied because the temporary file demo-patch.md was not present in the clone. Consequently I couldn’t produce a post‑patch SHA‑256 or an applied‑count.

Verified via OpenClaw 2026.5.6 + helmdeck v0.9.0-dev + openrouter/openai/gpt-oss-120b on 2026-05-07 (cost: $0.0705).

Developer reference (curl)

curl -fsS -X POST http://localhost:3000/api/v1/packs/fs.patch \
-H "Authorization: Bearer $JWT" -H 'Content-Type: application/json' \
-d "{
\"_session_id\":\"$SESSION\",
\"clone_path\":\"$CLONE\",
\"path\":\"docs-test-tmp.md\",
\"search\":\"test\",
\"replace\":\"capture\"
}"

Captured response (1 occurrence replaced):

{
"pack": "fs.patch",
"version": "v1",
"output": {
"applied": 1,
"sha256": "5d1ad738e3ae20fee43f2df6cf951d71a8917dc833daf5d474a71317f371b540"
},
"session_id": "f905a56c-f573-4c0f-b2b5-c73ca26ee318"
}

Error codes

CodeTriggers
invalid_inputpath-safety violation, file unreadable, search empty
session_unavailablesession expired
handler_failedunderlying file write fails

When applied=0: the search string didn't appear in the file. Not an error — the agent re-reads the file and decides what to do.

Session chaining

needs_session: true. Almost always between fs.read (verify-before) and fs.read (verify-after). Pair with git.commit to capture the patched state.

Async behavior

Synchronous. ~100 ms per patch.

See also