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>
- 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>
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>
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>
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>
A failed boot went straight back to the model as "fix it", so it guessed:
one draft raised its memory three rounds in a row while the real cause, an
empty setting the server can't run without, stayed untouched.
Now a failed boot is investigated first:
- Evidence: the server's distinct error lines (timestamps and repeats
removed), and a web search on the most telling one, with the top answer
read through the reader.
- Diagnosis: the model states the cause, the evidence for it, its
confidence and the exact fix. If unsure, it asks for up to 3 checks: a
search, a page, the files the server writes (a first boot of the draft),
or a boot test with settings or the startup command changed. Results go
back to it, two rounds at most.
- Fix: the model applies exactly the diagnosed fix and nothing unrelated.
- Attempt log: each cause, fix and outcome. A fix that changed nothing
rules its cause out; if the diagnosis returns to a ruled-out cause, the
build stops instead of guessing, and the PR lists what the builder found.
Boot rounds go up to 5, since rounds now end on a ruled-out cause rather
than a fixed count.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A draft built on a ready-made image whose files all live outside /server
went round three times and ended as WIP, although research had also
verified the game's Steam app, which SteamCMD installs straight into
/server. The build picked among the variant's images on its own.
- research reads each Steam app's OS list. When it ships a Linux server,
the SteamCMD route is the recommended option; for Windows-only servers,
images that bring Proton or Wine come first. The research prompt says
the same.
- A variant that can run on several images becomes one option per image
("... (SteamCMD)", "... (image x/y)"), so the admin picks exactly how the
server runs.
- build uses exactly the chosen option and never switches to another; if it
can't be made to work, the PR says so and another option can be picked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI's boot test gave every blank password-like setting a throwaway value. A
real deploy only fills x-generate fields, so a template whose server won't
start without one passed CI and failed for the first owner who deployed it.
boottest-payload now fills only x-generate fields, for CI and the builder
alike. The old behaviour is kept as -fill-blank-passwords, used only by
the builder's second boot to tell whether an empty setting is why a draft
fails. Messages and AUTHORING.md no longer say boot tests fill passwords;
a server that needs a join password gets x-generate.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Booting drafts as deployed catches a server that stops when a setting is
empty, but the server's own output rarely says why, so the model guessed
(more memory) for three rounds and the draft ended as WIP.
When a draft fails with its defaults and has blank password-like settings
without x-generate, the builder boots it once more with those filled. If
that boot passes, the cause is certain, and the model is told exactly that:
these settings stop the server when empty; give them x-generate (a password
per server) or a working default.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The builder's boot test filled every blank password-like setting with a
throwaway value. A real deploy only fills x-generate fields, so a draft whose
server refuses to run with an empty join password passed the builder and CI,
and then shut down on its first real deploy.
The builder now boots drafts with boottest-payload -as-deployed: only
x-generate fields are filled, everything else exactly as the template's
defaults. A server that won't run with its own defaults fails the build, and
the model is told that empty settings stay empty, so the value must come from
a default or x-generate. CI's boot test is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the worker is restarted mid-build (watchtower updating its image), the
boot test's container and volume are left behind, and the next boot test of
that game deploys under the same name. Remove them before each boot test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The draft branch was named after the job. Rerunning a build pushed onto the
earlier run's branch, which had review commits, so the push was rejected
and the finished build failed at its last step. Separate branches per run
would pile up open PRs for the same game instead.
A game now has one draft at a time, on draft/<id>. Before pushing, the
worker closes the game's open draft PRs (draft/<id>, and draft/<id>-<job>
as earlier versions named them) and deletes their branches, then
force-pushes draft/<id> and opens the new PR, which says which PR it
replaces. Other games' drafts and anyone's non-draft branches are never
touched.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Launch options must be documented. A draft passed an option its server
doesn't have; servers ignore unknown options, so it booted and passed every
boot test while its setting did nothing. build now reads the pages research
based the variant on, in full, and the drafter rejects any launch option in
the startup command that none of them, nor the image's own scripts, mention.
Shell tools' options (mkdir -p, [ -n ...]) are skipped; the check is off
without sources.
The builder is game-agnostic:
- AUTHORING.md (the model's rules), the research prompt and the schema
describe the rules without naming a game.
- The draft prompt no longer includes real templates from the repo. Its
format example is one invented server (no real command, options, ports or
app id), checked by TestExampleTemplateFollowsTheRules.
- Comments, help texts, BUILDER.md and test fixtures use invented names and
values.
- TestBuilderIsGameAgnostic reads every game's name and id from games/ and
fails if any appears in the builder's code, tests, rules, schemas or docs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The start-up check read the token's account with GET /user, which needs the
read:user scope. A token with only repository scope, which is all the worker
needs, got 403 and the worker exited in a restart loop. On 403 it now
names the commit author from BOT_NAME (default gbr-bot) and still checks
push access on the games repo.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The worker committed as a hard-coded "gbr-bot", and a token without Write
access only showed up when a build finished and its push failed. On start it
now reads the token's account (GET /user) for the commit author, and
refuses to run if that account can't push to the games repo.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The second Valheim build stopped before its boot test: told that
test.yaml's password "looks like a credential; map it under 'secrets'",
the model mapped it to a CI secret nobody created, and boottest-payload's
missing-secret exit ended the build.
- The validator now says to remove a test password first (boot tests fill
password settings with a throwaway value); secrets only when the server
can't start without a real one.
- Drafts with test.yaml 'secrets' fail the draft checks.
- build turns boottest-payload's missing-secret exit into feedback for the
model instead of an error.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nodes always publish container port 8211 and only move the host side. PublicPort (what the community server list announces) is now {PUBLIC_PORT_8211}, written unquoted, and the server binds -port=8211 directly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nodes always publish container port 25565, so an owner-set SERVER_PORT made Java Edition unreachable. The image already defaults to 25565; the status ping uses pingPort 25565 instead of reading the setting.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nodes always publish container port 7777, so an owner-set Port made the server unreachable. The startup command now passes Port=7777 directly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two problems a draft can boot fine with but break once an owner changes a
setting, both in the builder's Valheim draft:
- splitSettings: text settings used unquoted on the command line, directly
or through ARGS="-name ${SERVER_NAME}"; ./server $ARGS, so "GBRHosting -
My Server" reaches the game as several arguments. Suggests quoting or a
bash array.
- portSettings: game-port settings whose default isn't in defaultPorts, or
that owners can change although nodes always publish the same container
port. Internal ports (RCON, REST) and {PUBLIC_PORT_n} values are left out.
Both run on drafts only; AUTHORING.md gets the matching rules.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- research: searches SearXNG, reads the best pages through the reader, has the
model list the server's variants, then checks every Docker image and Steam
app it named for real before an admin sees it.
- build: first boot of the variant's images (skipping ones that need root),
a draft by the model, then the draft deployed with the node-agent's
BootTest like CI does; crashes, error lines and saves outside /server go
back to the model, three boots at most.
- worker: polls the panel's builder queue (outbound only), runs research or
build in a fresh clone, streams the output as the job log, and opens the
result as a pull request from the bot account.
- Dockerfile.builder, a workflow publishing gbr-builder:latest, and
BUILDER.md. Everything about where it runs comes from environment
variables; deployment files live outside this public repo.
draft now keeps its conversation in a drafter, so build can send boot
failures back to the same model session. The boot-test runner comment no
longer names its VM.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Validate took ~10 minutes, of which ~10 seconds was work: setup-go's
cache restore and save each waited ~4.7 minutes for a connect timeout,
because the runners' built-in cache server sits on a network job
containers can't reach. Turn off setup-go caching (three small modules
aren't worth it) and the game-test runner's cache server, so any cache
step skips at once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`gbrgames draft -name <game> -image <image> [-steam-app ID]
[-first-boot report.json]` has a model write games/<id>/game.yaml and
test.yaml, then checks them like any template and sends problems back
for up to three fix rounds. Ollama (default qwen3.8:27b, OLLAMA_URL) or
Gemini (GEMINI_API_KEY).
The model gets AUTHORING.md (new: the house rules, from how nodes really
run servers and today's bugs: x-generate passwords, no published RCON
ports, containerUser, heap below the limit, env var naming, ready lines),
the inspect-image facts, the first-boot report and the two smallest
templates as examples. With Ollama the answer is forced into the
template schema, tightened for drafts so ports, install config, settings
and their descriptions can't be left out or null.
On top of validation and the image checks, drafts must:
- be complete: player ports, >=3 settings each described, real memory,
a readyPattern or settleSec
- name settings so they reach the container as variables the image reads
(ServerPassword -> SERVER_PASSWORD when the image reads SERVER_PASS)
- not leave files first boot saw outside /server (lost on every start)
A first boot showing the image dies within seconds on permission errors
(it needs root) stops the draft before any model call. A missing
readyPattern is filled from first boot's verified suggestion.
Valheim, which we have no template for: lloesche/valheim-server was
rejected as root-only; via SteamCMD (app 896660) qwen3.8:27b on a local
3090 Ti wrote a passing template in two rounds (~1 min). It is about
70% there: it chose "--save-dir /server", which Valheim doesn't have
(-savedir), and missed -crossplay and 2457/udp. The save check is a text
check; booting the draft itself is the next step.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
First boot with the install fix ran ARK for ~10 minutes and found, in
ShooterGame.log: "Server: ... has successfully started!", "EOS ownership
validation initialized: Success", "Full Startup: 36.26 seconds" and,
about two minutes later, "Server has completed startup and is now
advertising for join. (9.96GB Mem)". That last line is when players,
console players through EOS included, can join, so it is the
readyPattern; the real line is test.yaml's readyExample. Boot tests now
wait for it instead of only checking ARK stays up for 10 minutes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>