cd /news/developer-tools/editing-a-godot-save-file-from-outsi… · home › topics › developer-tools › article
[ARTICLE · art-157531] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=↑ positive

Editing a Godot save file from outside the editor: what actually breaks

A developer built Save Value Editor, a Godot tool that edits save and config files by recording the byte span of each changed literal and replacing only that span, leaving the rest of the file byte-identical. The tool avoids the data corruption of naive JSON round trips, which turn 1.0 into 1, -0.0 into 0, and lose integers above 2^53, and it preserves Object(InputEventKey,...) lines, BOMs and CRLF endings that INI re-serialisation strips. It ships as a CLI with distinct exit codes for agents and as an in-editor dock for Godot 4.4+, verified against files written by Godot 4.4.1 through 4.7.2.

by read3 min views4 publishedOct 26, 2026

An agent (Codex, Claude Code, a CI job) can run a shell command, but it cannot click inside the Godot editor. So when the task is "put the game into this state" — progress/stage = 7, tutorial_done = true, audio/master = 0.42 — the agent ends up editing the save file directly. The editor itself can do the same thing through a dock; a terminal cannot.

The naive way is json.load → change → json.dump. On a Godot project that quietly changes data you did not ask to change. Below is what actually moved in the files I measured, and what an edit has to preserve to be safe.

JSON.parse_string() returns every JSON number as a float. A test file:

{"level":3,"big_int":9007199254740993,"float_one":1.0,"exp":1e10,"neg_zero":-0.0,"imprecise":0.30000000000000004,"tiny":1.0000000000000002e-300,"long_i64":9223372036854775807}

Any round trip through a float parser turns 1.0 into 1, 1e10 into 10000000000, -0.0 into 0, and 9007199254740993 (2^53+1) into a neighbour. 9223372036854775807 does not survive at all. A game that compares -0.0, prints a value back, or hashes its own save will notice the difference.

The safe rule: read numbers as source literals (text), not as floats, and write back the literals you did not touch.

ConfigFile.save() writes sections and ; comments, but if the file is project.godot with an input map it also contains whole Object(InputEventKey,...) values. Measured: a 1040-byte project.godot with two input events. An INI library re-serialises the file without those Object(...) lines, and the project no longer opens in the editor.

A 73-byte ConfigFile with a UTF-8 BOM, CRLF line endings and a ; comment: od -c starts with 357 273 277 and every line ends \r\n. Parsers that normalise newlines or strips the BOM change lines you never selected. Keep them or the diff is a lie.

var_to_bytes() files and .res are not text. An agent should refuse them, not "repair" them.

The tool I built opens the file with the engine's own parser to get labels and types, records the byte span of the literal being changed, and replaces exactly that span. Everything else is copied through untouched. Measured on the packaged build:

9007199254740993 → 1, "Player One" → "ニュー 名前", rest of the file byte-identical. project.godot with an input map: 1040 → 1038 bytes, both Object(InputEventKey,...) lines byte-identical afterwards, editor opened the project with no error.--restore --write: original SHA-256 back, exactly. Exit codes that separate the failure modes, so the agent can branch instead of guessing from prose:

0 wrote (or dry run OK), 2 missing or unreadable (binary) file, 3 label missing, null leaf, or a type change, 4 file is read-only.--write then produces exactly those bytes. I checked that equality on Godot 4.4.1, 4.5.2, 4.6.3 and 4.7.2 with files that each engine wrote itself..sve.bak) only on a real write, and no second backup when re-running the same edit (no_change).--json output. Note: the engine banner is the first line of stdout, so read the --quiet is not a workaround because it takes the JSON with it.

godot --headless --path <project> --script res://addons/save_value_editor/edit.gd -- \
  --file=user://settings.cfg --set=audio/master=0.42 --set=progress/stage=7 --write

--set writes the literal as given, so a string needs its own quotes. That is deliberate (no number reformatting) and it is the one place where the agent has to know the format. The same document code runs as a dock inside the editor, Godot 4.4+ — read a value list, filter it, change a value, see the diff before saving: Save Value Editor for Godot. The command/exit-code contract and its limits are public here: onpont/godot-save-value-cli.

── more in #developer-tools 4 stories · sorted by recency
multipeer.sh · · #developer-tools
v0.5.0
── more on @godot 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/editing-a-godot-save…] indexed:0 read:3min 2026-10-26 · —