Rules for making headless Godot (4.2+) development reproducible, avoiding drift and environment-specific failures.
Scope:
- Godot 4.2+ (Godot 3.x is out of scope)
Required rules (highest priority):
- Always use
--headless --path <PROJECT_DIR> (removes cwd dependency)
- Always capture logs under the project. Use
2>&1 | tee <PROJECT_DIR>/logs/<name>.log for commands
that terminate themselves, and capture Godot's status from ${PIPESTATUS[0]}. When timeout is
the only termination mechanism, redirect directly with > <PROJECT_DIR>/logs/<name>.log 2>&1
and capture timeout's status explicitly; do not pipe that run through tee.
- Always set project-local
XDG_DATA_HOME, XDG_CONFIG_HOME, and XDG_CACHE_HOME for any scripted Godot run. Set all three together — Godot writes to all three categories, and partial redirection still hits the unwritable default for the unset ones. Required to avoid CI/sandbox write failures (Can't open file for writing: ~/.config/godot/...); harmless on a developer machine because the only effect is that per-project metadata/cache live under <PROJECT_DIR>/.godot-xdg/ instead of the user's globals. Add .godot-xdg/ to .gitignore.
- These
exports affect every process started from the same shell, not just godot. If the same shell session also runs non-Godot tooling that honors XDG vars (e.g. a Node/Playwright browser-automation step for web-export testing), that tooling's own cache/data lookup silently moves under <PROJECT_DIR>/.godot-xdg/ too and can fail to find things it installed elsewhere (observed: Playwright reporting a missing browser executable). Export these three vars only in the specific command/subshell that invokes godot, or explicitly unset/restore them before running unrelated tools in the same shell.
- Never edit
.tscn as raw text (edits must go through --headless --script)
- For repeated validation commands, standardize them as
tools/*.sh; after manual tweaks, re-run via the same scripts
- If
res://tools/godot_apply_patch.gd is missing, copy tools/godot_apply_patch.gd, resolved relative to this loaded SKILL.md, to <PROJECT_DIR>/tools/godot_apply_patch.gd before running patch commands
- Keep startup smoke and logic tests separate: smoke starts
run/main_scene; run_tests.gd holds project-specific logic checks
Minimum smoke wrapper template
For repeated startup verification, standardize as <PROJECT_DIR>/tools/smoke.sh. Other repeated invocations (run_tests.sh, export_web.sh, ...) follow the same shape — only the godot arguments and the log filename change.
#!/usr/bin/env bash
set -euo pipefail
PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
# Project-local XDG (always; see Required rules above).
export XDG_DATA_HOME="$PROJECT_DIR/.godot-xdg/data"
export XDG_CONFIG_HOME="$PROJECT_DIR/.godot-xdg/config"
export XDG_CACHE_HOME="$PROJECT_DIR/.godot-xdg/cache"
mkdir -p "$XDG_DATA_HOME" "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME" "$PROJECT_DIR/logs"
# Smoke = launch run/main_scene headlessly, quit after a few frames.
# --quit-after <N> bounds the run by frame count.
godot --headless --path "$PROJECT_DIR" --quit-after 60 \
2>&1 | tee "$PROJECT_DIR/logs/smoke.log"
echo "godot_exit=${PIPESTATUS[0]}"
Pass criteria for smoke:
- Captured exit code (
${PIPESTATUS[0]}) is 0.
logs/smoke.log contains no ERROR, SCRIPT ERROR, Failed to load, or Parse Error line outside the project's known-warning whitelist (see references/headless_cli.md).
- A startup-visible marker is present (e.g. a
print() from the main scene's _ready, or absence of "Main scene can't be loaded").
If you get stuck, provide:
- Full command lines and
logs/*.log
godot --version output
- Whether
export_presets.cfg exists (when exporting)
Out of scope:
- GUI/OS-specific setup beyond the project-local XDG wrapper
- Level design, render validation, performance tuning
- Game-specific scoring, win/loss rules, controls, and simulation policies
When you need details, read these files (relative to this skill's base directory):
references/headless_cli.md — CLI conventions, XDG setup, known warnings
references/export_and_import.md — export/import rules
references/scene_editing_via_godot.md — safe .tscn editing, patch JSON schema and operations
references/testing_headless.md — headless testing strategy
If unsure about Godot CLI or features, consult:
1---2name: running-headless-godot3description: Runs reproducible headless Godot 4.2+ workflows for CLI commands, exports, scripted scene edits, and tests. Use when running Godot CLI commands, editing .tscn via script, capturing logs, or exporting in headless mode.4---56Rules for making headless Godot (4.2+) development reproducible, avoiding drift and environment-specific failures.78Scope:9- Godot 4.2+ (Godot 3.x is out of scope)1011Required rules (highest priority):12- Always use `--headless --path <PROJECT_DIR>` (removes `cwd` dependency)13- Always capture logs under the project. Use `2>&1 | tee <PROJECT_DIR>/logs/<name>.log` for commands14 that terminate themselves, and capture Godot's status from `${PIPESTATUS[0]}`. When `timeout` is15 the only termination mechanism, redirect directly with `> <PROJECT_DIR>/logs/<name>.log 2>&1`16 and capture `timeout`'s status explicitly; do not pipe that run through `tee`.17- Always set project-local `XDG_DATA_HOME`, `XDG_CONFIG_HOME`, **and** `XDG_CACHE_HOME` for any scripted Godot run. Set all three together — Godot writes to all three categories, and partial redirection still hits the unwritable default for the unset ones. Required to avoid CI/sandbox write failures (`Can't open file for writing: ~/.config/godot/...`); harmless on a developer machine because the only effect is that per-project metadata/cache live under `<PROJECT_DIR>/.godot-xdg/` instead of the user's globals. Add `.godot-xdg/` to `.gitignore`.18 - These `export`s affect every process started from the same shell, not just `godot`. If the same shell session also runs non-Godot tooling that honors XDG vars (e.g. a Node/Playwright browser-automation step for web-export testing), that tooling's own cache/data lookup silently moves under `<PROJECT_DIR>/.godot-xdg/` too and can fail to find things it installed elsewhere (observed: Playwright reporting a missing browser executable). Export these three vars only in the specific command/subshell that invokes `godot`, or explicitly unset/restore them before running unrelated tools in the same shell.19- Never edit `.tscn` as raw text (edits must go through `--headless --script`)20- For repeated validation commands, standardize them as `tools/*.sh`; after manual tweaks, re-run via the same scripts21- If `res://tools/godot_apply_patch.gd` is missing, copy `tools/godot_apply_patch.gd`, resolved relative to this loaded `SKILL.md`, to `<PROJECT_DIR>/tools/godot_apply_patch.gd` before running patch commands22- Keep startup smoke and logic tests separate: smoke starts `run/main_scene`; `run_tests.gd` holds project-specific logic checks2324## Minimum smoke wrapper template2526For repeated startup verification, standardize as `<PROJECT_DIR>/tools/smoke.sh`. Other repeated invocations (`run_tests.sh`, `export_web.sh`, ...) follow the same shape — only the godot arguments and the log filename change.2728```bash29#!/usr/bin/env bash30set -euo pipefail31PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"3233# Project-local XDG (always; see Required rules above).34export XDG_DATA_HOME="$PROJECT_DIR/.godot-xdg/data"35export XDG_CONFIG_HOME="$PROJECT_DIR/.godot-xdg/config"36export XDG_CACHE_HOME="$PROJECT_DIR/.godot-xdg/cache"37mkdir -p "$XDG_DATA_HOME" "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME" "$PROJECT_DIR/logs"3839# Smoke = launch run/main_scene headlessly, quit after a few frames.40# --quit-after <N> bounds the run by frame count.41godot --headless --path "$PROJECT_DIR" --quit-after 60 \42 2>&1 | tee "$PROJECT_DIR/logs/smoke.log"43echo "godot_exit=${PIPESTATUS[0]}"44```4546Pass criteria for smoke:47- Captured exit code (`${PIPESTATUS[0]}`) is `0`.48- `logs/smoke.log` contains no `ERROR`, `SCRIPT ERROR`, `Failed to load`, or `Parse Error` line outside the project's known-warning whitelist (see `references/headless_cli.md`).49- A startup-visible marker is present (e.g. a `print()` from the main scene's `_ready`, or absence of "Main scene can't be loaded").5051If you get stuck, provide:52- Full command lines and `logs/*.log`53- `godot --version` output54- Whether `export_presets.cfg` exists (when exporting)5556Out of scope:57- GUI/OS-specific setup beyond the project-local XDG wrapper58- Level design, render validation, performance tuning59- Game-specific scoring, win/loss rules, controls, and simulation policies6061When you need details, read these files (relative to this skill's base directory):62- `references/headless_cli.md` — CLI conventions, XDG setup, known warnings63- `references/export_and_import.md` — export/import rules64- `references/scene_editing_via_godot.md` — safe `.tscn` editing, patch JSON schema and operations65- `references/testing_headless.md` — headless testing strategy6667If unsure about Godot CLI or features, consult:68- https://docs.godotengine.org/en/4.4/tutorials/editor/command_line_tutorial.html69- https://docs.godotengine.org/en/4.4/tutorials/export/exporting_for_dedicated_servers.html70- https://docs.godotengine.org/en/stable/tutorials/export/exporting_projects.html