The AI builder could call a template working when a node would break it, and a long build could lose its own facts. This PR fixes both, adds checks that don't depend on the model, and lets the builder repair existing templates.
A pass now means it works the way a node runs it
One deploy path. Every boot the builder does, first boots included, goes through the node-agent's own deploy code (BootTest). It gets exactly the mounts, user and restart behaviour nodes use. The separate Go copy of the deploy, which had drifted, is gone.
Every lost file counts. Any file the server writes outside /server and /data counts, not only config-like files. That includes the image's own VOLUME folders, which a node recreates empty on every start. Docker's own init file, the Steam client's own state, logs, and lock and pid files are left out.
Stays up, and listens on its ports. Boot tests in CI and in the builder need the server to stay up for 60 s after its ready line (holdSec in test.yaml). Once up, it must listen on every published port (expectPorts, from defaultPorts). A port that only opens when a player connects goes in the template's test.yaml as unboundPorts, with the reason. One template uses this so far.
Real settings. Boot tests fill only x-generate fields, as the panel does. The builder's diagnostic second boot still fills blank passwords, to tell a missing password apart from a broken install.
Steam download cache
Each Steam app's install is kept in a Docker volume (gbr-steamcache-<app>, label gbr.cache=steam). The cache is updated once per run, and each boot's server volume is seeded from it, so a repeat boot checks files instead of downloading the whole game. STEAM_CACHE=off turns it off. RUNNER.md now gives prune commands that keep the cache and the gbr-boottest network. Never use docker system prune on a runner.
Model requests stay a fixed size
Each drafting round sends:
the rules and the facts;
the last answer;
the current problems;
one line per earlier round.
It no longer re-sends the whole growing conversation, which overran the model's context in long builds. Token use is logged, with a warning near the limit. Image scripts and diagnose output are capped.
Safety and review
A deterministic check rejects unsafe startup and install commands:
downloads piped into a shell, base64-decoded scripts, eval;
reverse shells, sudo/su, setuid;
writes to /etc; cron, systemctl, iptables, mount and kernel modules;
miners.
The PR summary now separates three things:
what the builder measured itself;
the download addresses nodes will fetch (review these);
what the model read in its sources.
Our own sites are dropped from search results.
Keeping templates working
gbrgames repair -id <game> boots an existing template as nodes run it. If it fails, the builder diagnoses it and opens a PR with the fix. A template that boots is left alone ("nothing to repair"). The worker takes repair jobs, which the panel queues from a game's page.
Weekly check. CI boot-tests every game on Mondays at 06:00 UTC. For each game that fails, it posts games_boot_failed to the panel, which queues a repair. This uses the existing PANEL_WEBHOOK_SECRET.
gbrgames eval builds existing games blind in a temp folder and scores each draft against the real template: route, ports, ready line and settings. Use it to check builder changes.
Merge order
gbreed-public/gbr-node-agent#30 first. The builder's first boots need its keep mode, and the hold and port checks only run with that agent.
This PR.
Then the panel and client PRs: repair jobs, the builder status, and a lighter job list.
After merging, boot-test all games (Actions → Validate game templates → Run workflow, all): boot tests now also check the hold and the ports.
The AI builder could call a template working when a node would break it, and a long build could lose its own facts. This PR fixes both, adds checks that don't depend on the model, and lets the builder repair existing templates.
### A pass now means it works the way a node runs it
- **One deploy path.** Every boot the builder does, first boots included, goes through the node-agent's own deploy code (BootTest). It gets exactly the mounts, user and restart behaviour nodes use. The separate Go copy of the deploy, which had drifted, is gone.
- **Every lost file counts.** Any file the server writes outside `/server` and `/data` counts, not only config-like files. That includes the image's own `VOLUME` folders, which a node recreates empty on every start. Docker's own init file, the Steam client's own state, logs, and lock and pid files are left out.
- **Stays up, and listens on its ports.** Boot tests in CI and in the builder need the server to stay up for 60 s after its ready line (`holdSec` in test.yaml). Once up, it must listen on every published port (`expectPorts`, from `defaultPorts`). A port that only opens when a player connects goes in the template's test.yaml as `unboundPorts`, with the reason. One template uses this so far.
- **Real settings.** Boot tests fill only `x-generate` fields, as the panel does. The builder's diagnostic second boot still fills blank passwords, to tell a missing password apart from a broken install.
### Steam download cache
Each Steam app's install is kept in a Docker volume (`gbr-steamcache-<app>`, label `gbr.cache=steam`). The cache is updated once per run, and each boot's server volume is seeded from it, so a repeat boot checks files instead of downloading the whole game. `STEAM_CACHE=off` turns it off. RUNNER.md now gives prune commands that keep the cache and the `gbr-boottest` network. Never use `docker system prune` on a runner.
### Model requests stay a fixed size
Each drafting round sends:
- the rules and the facts;
- the last answer;
- the current problems;
- one line per earlier round.
It no longer re-sends the whole growing conversation, which overran the model's context in long builds. Token use is logged, with a warning near the limit. Image scripts and diagnose output are capped.
### Safety and review
- A deterministic check rejects unsafe startup and install commands:
- downloads piped into a shell, base64-decoded scripts, `eval`;
- reverse shells, `sudo`/`su`, setuid;
- writes to `/etc`; cron, systemctl, iptables, mount and kernel modules;
- miners.
- The PR summary now separates three things:
- what the builder measured itself;
- the download addresses nodes will fetch (review these);
- what the model read in its sources.
- Our own sites are dropped from search results.
### Keeping templates working
- **`gbrgames repair -id <game>`** boots an existing template as nodes run it. If it fails, the builder diagnoses it and opens a PR with the fix. A template that boots is left alone ("nothing to repair"). The worker takes `repair` jobs, which the panel queues from a game's page.
- **Weekly check.** CI boot-tests every game on Mondays at 06:00 UTC. For each game that fails, it posts `games_boot_failed` to the panel, which queues a repair. This uses the existing `PANEL_WEBHOOK_SECRET`.
- **`gbrgames eval`** builds existing games blind in a temp folder and scores each draft against the real template: route, ports, ready line and settings. Use it to check builder changes.
### Merge order
1. gbreed-public/gbr-node-agent#30 first. The builder's first boots need its keep mode, and the hold and port checks only run with that agent.
2. This PR.
3. Then the panel and client PRs: repair jobs, the builder status, and a lighter job list.
After merging, boot-test all games (Actions → Validate game templates → Run workflow, `all`): boot tests now also check the hold and the ports.
### Tested
- `go test ./...` passes. New tests cover:
- the lost-file filter;
- payload ports;
- the unsafe-command check;
- fixed-size requests;
- PR text, draft scoring and repair results.
- Run locally against a node-agent built from #30:
- a boot test with the hold and port checks;
- an untouched first boot through the agent;
- two Steam first boots, the second seeded from the cache.
That run is what found the false "files lost" report from Docker's init file and the Steam client's config, fixed here.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
What a builder boot sees is now what a node does:
- First boots (untouched, or -as-deployed with exactly a new server's
settings) are deployed by the node-agent's BootTest in keep mode; the
builder only watches the console and inspects what's left. The Go copy of
the deploy (its own docker run, mounts and startup prefix) is gone. It had
drifted: it gave the image's VOLUME folders their own volumes, which nodes
don't, so saves there looked safe while every start loses them.
- Files written outside /server and /data are counted whatever their type
(it only counted config-like extensions, so .sav or .db saves went
unseen), including the image's VOLUME folders; logs, pid and lock files
and system folders are left out.
- Boot tests ask the server to stay up holdSec (60 s, test.yaml can set it)
after its ready line and to listen on every published container port
(needs the node-agent's matching BootTest).
- Steam apps are downloaded once per build into a gbr.cache volume and copied
into each boot, so later boots only check the files (STEAM_CACHE=off).
- RUNNER.md no longer suggests docker system prune, which deletes the
gbr-boottest network when nothing is using it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Some servers open a published port only once a player connects (through a
proxy, for example), so the boot test's port check can't see it after boot.
A template lists those ports as unboundPorts in its test.yaml, with the
reason. The first template that needs it lists one UDP port, until a real
client confirms how players reach it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every container the node-agent starts gets /usr/sbin/docker-init, and SteamCMD
rewrites its own config.vdf on every run. Counted as files written outside the
volumes, they made every build look like it loses saves. A folder whose only
changes were logs no longer counts either, and a folder followed by a
same-named file (worlds, worlds.bak) is no longer taken for a file.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The drafter no longer grows one conversation. Each request is the rules
and facts, the model's latest template, what is wrong now and one line per
earlier round, so long builds can't push the facts out of the model's
32K context (Ollama drops the oldest part silently). The image's scripts
in the facts are capped at 24000 characters in all, and diagnosis check
results at 16000.
- Every model call logs its prompt and answer tokens, with a warning near
the context limit.
- unsafeCommands: a draft whose startup command or installConfig pipes a
download into a shell, decodes base64, uses eval, raw network tools or
/dev/tcp, sudo/su, setuid bits, writes /etc, touches cron/systemd/firewall,
or looks like a miner goes back to the model. AUTHORING.md has the rule.
- The PR summary lists every address nodes would download from under
"Review carefully", marks the measured checks as measured, and labels the
research notes as the model's reading, not checked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The builder could only create games; nothing noticed when a live template
stopped working, and there was no way to tell whether a builder change made
it better or worse.
- repair -id ID: boots an existing template as nodes run it; if it fails,
the same investigation as for a draft runs and the model changes only what
the diagnosis says. The worker handles "repair" jobs (gameId from the
panel) and opens "fix(games): repair ..." on draft/<id>; a template that
boots as it is reports "nothing to repair" and opens no PR. The boot and
fix loop is now shared by builds and repairs.
- validate.yml runs weekly and boot-tests every game; failed games are sent
to the panel (X-GBR-Event: games_boot_failed) to queue repairs.
- eval -ids all: builds games that already have a template from scratch,
blind, and scores each draft against it (boots, same image and app, ports,
ready line, settings found) into eval.md and eval.json.
- research skips our own sites, so neither research nor eval reads our
templates back.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
The AI builder could call a template working when a node would break it, and a long build could lose its own facts. This PR fixes both, adds checks that don't depend on the model, and lets the builder repair existing templates.
A pass now means it works the way a node runs it
/serverand/datacounts, not only config-like files. That includes the image's ownVOLUMEfolders, which a node recreates empty on every start. Docker's own init file, the Steam client's own state, logs, and lock and pid files are left out.holdSecin test.yaml). Once up, it must listen on every published port (expectPorts, fromdefaultPorts). A port that only opens when a player connects goes in the template's test.yaml asunboundPorts, with the reason. One template uses this so far.x-generatefields, as the panel does. The builder's diagnostic second boot still fills blank passwords, to tell a missing password apart from a broken install.Steam download cache
Each Steam app's install is kept in a Docker volume (
gbr-steamcache-<app>, labelgbr.cache=steam). The cache is updated once per run, and each boot's server volume is seeded from it, so a repeat boot checks files instead of downloading the whole game.STEAM_CACHE=offturns it off. RUNNER.md now gives prune commands that keep the cache and thegbr-boottestnetwork. Never usedocker system pruneon a runner.Model requests stay a fixed size
Each drafting round sends:
It no longer re-sends the whole growing conversation, which overran the model's context in long builds. Token use is logged, with a warning near the limit. Image scripts and diagnose output are capped.
Safety and review
eval;sudo/su, setuid;/etc; cron, systemctl, iptables, mount and kernel modules;Keeping templates working
gbrgames repair -id <game>boots an existing template as nodes run it. If it fails, the builder diagnoses it and opens a PR with the fix. A template that boots is left alone ("nothing to repair"). The worker takesrepairjobs, which the panel queues from a game's page.games_boot_failedto the panel, which queues a repair. This uses the existingPANEL_WEBHOOK_SECRET.gbrgames evalbuilds existing games blind in a temp folder and scores each draft against the real template: route, ports, ready line and settings. Use it to check builder changes.Merge order
After merging, boot-test all games (Actions → Validate game templates → Run workflow,
all): boot tests now also check the hold and the ports.Tested
go test ./...passes. New tests cover:Run locally against a node-agent built from #30:
That run is what found the false "files lost" report from Docker's init file and the Steam client's config, fixed here.
🤖 Generated with Claude Code
🤖 AI Code Review
⚠️ The review feedback was empty or could not be generated.
Automated review by AI Git Bot