fix(tooling): build first, then start the app, then list it

The notes had agents start `crafter-build <flag> -r` in the background
and run `crafter-debug ls --wait 60s` straight after. That raced the
build: the first agent to follow them ran the plain build's stub, which
said "no debug build yet" and exited, since the client `ls --wait` needs
is one of the things the debug build compiles, and it polled by hand
until the build was done.

The race came from the order, so the order changes: build with the flag
in the foreground, which ends once the app and the client are built,
then start the app with -r in the background, then `ls --wait`, which
now only waits for what it is for, the app registering. A launcher that
polls for the build instead was tried and dropped: a shell loop that
reruns itself each second, and waits out its whole --wait when no build
is coming at all.

The consumer notes, the client's guide and the README say so, and the
stub and the not-yet-built launcher name the step that was skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jorijn van der Graaf 2026-11-01 18:24:35 +01:00
commit 8e7f8c5f3b
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
4 changed files with 14 additions and 10 deletions

View file

@ -12,7 +12,8 @@ Runtime debugging for AI agents, and for people. An app built with Crafter.Debug
Breakpoints and memory stay with a debugger; `crafter-debug debugger` prints how to attach one. Breakpoints and memory stay with a debugger; `crafter-debug debugger` prints how to attach one.
```console ```console
$ crafter-build --debug-api -r & # the app's debug build, in the background $ crafter-build --debug-api # the app's debug build, and the client with it
$ crafter-build --debug-api -r & # the app, in the background
$ ./build/crafter-debug ls --wait 60s $ ./build/crafter-debug ls --wait 60s
game id 4f1c09a2b7e3d851 pid 3 (sandbox 48211) up 4s port 41207 game id 4f1c09a2b7e3d851 pid 3 (sandbox 48211) up 4s port 41207
$ ./build/crafter-debug call Lobby_Start && ./build/crafter-debug wait --frames 30 $ ./build/crafter-debug call Lobby_Start && ./build/crafter-debug wait --frames 30

View file

@ -974,13 +974,15 @@ memory are a debugger's job; `crafter-debug debugger` prints how to attach one.
## The loop ## The loop
1. Build and start the app from its debug build, in the background (it does 1. Build with the debug flag, in the foreground: `crafter-build --debug-api`
not exit): `crafter-build --debug-api -r` (use the project's own flag). (use the project's own flag). That build compiles this client too.
2. `./build/crafter-debug ls --wait 60s` until it is listed. 2. Start the app in the background, since it does not exit:
3. `cmds` and `help <command>` to see what it offers, `get '*'` for its state. `crafter-build --debug-api -r`.
4. Act and observe: `call`, `set`, `pause --when`, `wait`, `step`, `trace`, 3. `./build/crafter-debug ls --wait 60s` until it is listed.
4. `cmds` and `help <command>` to see what it offers, `get '*'` for its state.
5. Act and observe: `call`, `set`, `pause --when`, `wait`, `step`, `trace`,
`log --level warn`, `screenshot` (open the PNG it prints with Read). `log --level warn`, `screenshot` (open the PNG it prints with Read).
5. `quit` when done. Killing the background task may leave the app running 6. `quit` when done. Killing the background task may leave the app running
if crafter-build's alias lacks --die-with-parent; `ls` shows leftovers. if crafter-build's alias lacks --die-with-parent; `ls` shows leftovers.
## Commands ## Commands

View file

@ -4,7 +4,8 @@ This app can be driven while it runs: call its commands, read and set its state,
### Driving it ### Driving it
- Start the debug build in the background, since it does not exit: `crafter-build <debug flag> -r`. - Build with the debug flag first, in the foreground: `crafter-build <debug flag>`. That build also compiles the client `./build/crafter-debug` runs. The first one takes a few minutes.
- Then start the app in the background, since it does not exit: `crafter-build <debug flag> -r`.
- From the project directory, use `./build/crafter-debug`: - From the project directory, use `./build/crafter-debug`:
- `ls --wait 60s` until it is listed; `status`, `cmds`, `help <command>`, `get '*'` - `ls --wait 60s` until it is listed; `status`, `cmds`, `help <command>`, `get '*'`
- `call <command> key=value ...`, `set <path> <value>` - `call <command> key=value ...`, `set <path> <value>`

View file

@ -87,7 +87,7 @@ namespace Crafter::Debug {
"# project with {0}: runs the crafter-debug client that build compiled.\n" "# project with {0}: runs the crafter-debug client that build compiled.\n"
"client={1}\n" "client={1}\n"
"if [ ! -x \"$client\" ]; then\n" "if [ ! -x \"$client\" ]; then\n"
" echo \"crafter-debug: the client is not built yet; build with: crafter-build {0}\" >&2\n" " echo \"crafter-debug: the client is not built yet; let crafter-build {0} finish first, which builds it with the app\" >&2\n"
" exit 2\n" " exit 2\n"
"fi\n", "fi\n",
flag, ShellQuote(client.string())); flag, ShellQuote(client.string()));
@ -113,7 +113,7 @@ namespace Crafter::Debug {
return std::format( return std::format(
"#!/bin/sh\n" "#!/bin/sh\n"
"# Written by Crafter.Debug (Debug::Use): this project has no debug build yet.\n" "# Written by Crafter.Debug (Debug::Use): this project has no debug build yet.\n"
"echo \"crafter-debug: no debug build yet; build with: crafter-build {}\" >&2\n" "echo \"crafter-debug: no debug build yet; build with: crafter-build {0}, then start the app in the background with: crafter-build {0} -r\" >&2\n"
"exit 2\n", "exit 2\n",
flag); flag);
} }