myxce/.claude/skills/xce-build/SKILL.md
Rongsong Shen 651b513850
fix(xce-build skill): dispatch builds via Bash run_in_background, not Agent subagent
Replaces the "always dispatch via the Agent tool" instruction with a
direct Bash-tool run_in_background pattern, following the harness's
own task-notification mechanism instead of trusting a subagent's
self-reported "I'll report back."

Confirmed broken 2026-09-03: a general-purpose subagent given the
exact "wait for it, do not background it" instruction redirected the
build to a log file, announced it would report back once finished,
and returned immediately with no result -- its own Agent-tool call
showed completed with nothing to relay. The harness did eventually
deliver a second, fuller notification for that same subagent several
minutes later, but a controller cannot rely on that arriving, per the
prior "fork claimed will report back, never reachable in a new
session" incident already logged in .wolf/cerebrum.md.

The new pattern (proven reliable twice this session: a full `all
debug` build and a `controller` ASAN rebuild) runs the resolved
command directly with Bash's own run_in_background, waits for the
harness's real completion notification, then reads only the log's
tail plus a targeted error grep -- keeping context usage low without
depending on a subagent's compliance.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YAWMhfRJFnmmnG8h97yUDd
2026-09-03 09:09:42 +00:00

12 KiB

name description disable-model-invocation allowed-tools
xce-build Build XCE components with tests. Usage: /xce-build [component] [profile] [build_dir] [--incremental]. Components: all (default), lib, controller, scheduler, worker, apiserver, xcecli, xceadmin. Profiles: asan (default), debug (non-ASAN -O0 -g3 for gdb/cores), release. false Bash(*)

build xce source code

Args: [component] [profile|build_dir] [build_dir] [--incremental] — passed as free text after /xce-build, e.g. /xce-build controller debug. --incremental can appear anywhere in the words (it's stripped out before the rest are parsed positionally — see below). Parse the ARGUMENTS text yourself and run the command below with the parsed values inline.

  • No context: fork, intentionally. It was tried (twice) and reproducibly breaks this skill: whenever ARGUMENTS is non-empty (e.g. controller), the forked subagent receives no usable task — it responds as if handed only ambient project context, never runs the build, and reports nothing back. Bare /xce-build (no args) happens to still "succeed" under context: fork only because the empty-arg case is indistinguishable from no task at all failing open into some default — not because forking works. Do not re-add context: fork here without first confirming the harness actually threads a non-empty ARGUMENTS payload into the forked subagent's prompt.
  • component (1st word, default all) — all, lib, controller, scheduler, worker, apiserver, xcecli, xceadmin
  • profile (2nd word, default asan) — asan (debug + AddressSanitizer → xce.build), debug (non-ASAN -O0 -g3 -fno-inline -fno-omit-frame-pointer → build.dbg), release (-O3, no sanitizer → build.release). Each profile uses its own default build dir so the variants never clobber each other. If the 2nd word isn't one of these three keywords, treat it as a legacy explicit build_dir instead (3rd word, if present, always overrides the build dir).
  • Use the debug profile to root-cause heisenbug crashes that ASAN perturbs away and that gdb-on-release turns into a hang/timeout — the non-ASAN -O0 -g3 build reproduces them and gives a fully symbolized backtrace under gdb (how bug-1282/bug-1286 were cracked).
  • controller always builds xce_test too (meson compile … xce_controller controller_unit_test controller/xce_test), in every profile — not just asan. xce_test is the driver /xce-test-ha needs; you never have to build it as a separate step after /xce-build controller, and the two binaries in a build dir are always the same commit/profile in lockstep.
  • xceadmin is Python, meson-driven (src/xceadmin/meson.build's custom_target wraps meson-build.sh), and ignores the ASAN/debug/release sanitizer semantics entirely — xce-build.sh's xceadmin case just runs meson compile -C "$BUILD_DIR" xceadmin, the same target nix build .#xcecore and every other component use; the profile arg is accepted but has no effect on it (every invocation does the identical pinned, non-editable, out-of-tree install + test, regardless of asan/debug/release — unlike the old editable-install-by-default behavior, this always exercises real packaged schema resolution, not just a release run). For fast iterative dev without a full meson reconfigure, xceadmin can still be built standalone: cd src/xceadmin && pip install -e . (see meson-build.sh for the exact out-of-tree steps the meson target itself runs). Every invocation syncs the bundled controller.capnp/c.capnp and the 7 JSON validation schemas from src/schema/ and src/configs/schema/ into xcecommon's packaged schema_files/, installs pinned into a fresh build-dir venv, runs pytest against that install, and — only after tests pass — builds an installable wheel from that same schema-synced tree into $BUILD_DIR/xceadmin/dist/xceadmin-<version>-py3-none-any.whl (pip wheel … --no-deps, so it's xceadmin's own wheel only, not a vendored bundle of click/pycapnp/etc). xce-build.sh prints the wheel's path and a ready-to-run pip install "<path>" line after the build. Requires the nix dev shell (pycapnp, pyyaml, jsonschema, click, pytest, ruff — all provided by src/nixSupport/xce.nix).
  • --incremental (bug-1565 escape hatch, opt-in, NOT the default): every invocation normally does rm -rf "$BUILD_DIR" + a full fresh meson setup, unconditionally — correct in isolation, but since the actual compile step afterward only rebuilds the ONE requested component, a plain single-component build silently deletes any OTHER already-built binary sharing that dir (e.g. /xce-build xceadmin after /xce-build controller wipes xce_controller). Pass --incremental to reconfigure the existing dir in place instead (meson setup --reconfigure), preserving whatever else was already built there — use it specifically when you want a sibling component alongside an already-built one, e.g. /xce-build controller then /xce-build xceadmin --incremental to end up with both in the same dir at once (needed for /xce-deploy-ha, which wants xce_controller + xcecd_virtual + xce_scheduler + the xceadmin wheel all together). Deliberately not the default: this project's capnp-generation custom_target has documented, unfixed missing-dependency-edge races (buglog.json — normally papered over as a loud, retry-fixable failure on a clean build; skipping the wipe could instead let the same gap cause silent staleness). If anything looks wrong after an incremental build, rebuild without the flag.

Examples: /xce-build controller (ASAN), /xce-build controller debug (gdb/core build), /xce-build all release, /xce-build xceadmin (build + test + wheel, any profile arg is accepted and ignored), /xce-build xceadmin --incremental (same, but keeps other components already built in the same dir).

Run (substituting the parsed component/profile/build_dir; omit trailing empty ones; append --incremental at the end, anywhere, when requested):

nix develop ./src --command bash src/tools/xce-build.sh <component> <profile> <build_dir> [--incremental]

e.g. for /xce-build controller: nix develop ./src --command bash src/tools/xce-build.sh controller. For /xce-build xceadmin --incremental: nix develop ./src --command bash src/tools/xce-build.sh xceadmin --incremental.

Run the build via Bash run_in_background, not an Agent-tool subagent

Do not dispatch the build to an Agent-tool subagent (confirmed broken 2026-09-03: a general-purpose subagent given this exact instruction redirected the build to a log file, announced "I've kicked off the build in the background... I'll report back once it finishes," and returned immediately — its own Agent-tool call then shows as completed with no build result. That "I'll report back" is not reliable: the parent has already regained control and cannot assume a second notification is coming. In this occurrence the harness did eventually deliver a second, fuller notification for the same subagent several minutes later, but a controller cannot bank on that — a near-identical pattern in an unrelated cross-session case never came back at all, per the OpenWolf cerebrum warning "A fork/subagent dispatched in a prior session that claimed 'build/tests in progress, will report back' is NOT reachable in a new session." A model instructed in plain English to "wait, don't background it" backgrounding it anyway is the actual failure mode — a differently-worded subagent prompt is not a reliable fix for that).

Instead, run the resolved command directly with the Bash tool's own run_in_background: true, from the current session, and let the harness's own task-notification mechanism — not a subagent's self-report — tell you when it's done. This has been empirically proven reliable in this exact repo (2026-09-03: both a full all debug build and a controller ASAN rebuild were driven this way, and each genuinely blocked until real completion).

nix develop ./src --command bash src/tools/xce-build.sh <component> <profile> <build_dir> [--incremental] \
  > <scratchpad>/xce-build-<component>-<profile>.log 2>&1; \
  echo "EXIT_CODE=$?" >> <scratchpad>/xce-build-<component>-<profile>.log

Call this once with run_in_background: true (substitute the resolved component/profile/build_dir and a scratchpad log path first — do not make anything downstream re-parse ARGUMENTS). Then stop and wait; do not poll, sleep-loop, or call ScheduleWakeup — the harness delivers a <task-notification> the moment the process exits, exactly like any other backgrounded Bash command. When it arrives, read the log's tail (e.g. tail -30) plus a targeted grep -nE "error:|FAILED|undefined reference|Traceback" rather than the whole log, to get the meson test Ok/Fail summary line and any failures without pulling the full (often thousands-of-lines) compiler output into context. Then ls -la the expected output binaries to confirm fresh mtimes, the same verification this skill has always asked for — just performed directly instead of trusted from a subagent's claim.

This is deliberately not context: fork either — that frontmatter field reproducibly failed to thread a non-empty ARGUMENTS payload into a forked subagent (see above). Running the resolved command directly via Bash/run_in_background sidesteps both failure modes: there is no subagent in the loop to either drop the ARGUMENTS payload or silently background-and-return.

Installing the xceadmin wheel elsewhere

/xce-build xceadmin always leaves a fresh, tested wheel at <build_dir>/xceadmin/dist/xceadmin-<version>-py3-none-any.whl (e.g. xce.build/xceadmin/dist/xceadmin-0.1.0-py3-none-any.whl). It's a normal wheel — pip install it anywhere a compatible Python is available, no nix or meson needed at install time:

pip install /path/to/xce.build/xceadmin/dist/xceadmin-0.1.0-py3-none-any.whl

The wheel bundles its own schema_files/ (the same controller.capnp, c.capnp, and 7 JSON schemas synced in above) so xceadmin config build works fully standalone against any src/configs/-shaped directory — it does not need to run from inside this repo or the nix dev shell. Its 4 real runtime imports (click, pycapnp, PyYAML, jsonschema — pyproject.toml's dependencies, verified against every actual import in src/xceadmin/src/xcecommon) are declared, so a plain pip install resolves them from PyPI on its own; the one caveat is pycapnp, whose prebuilt PyPI wheels are usually fine outside the nix shell but aren't the same guaranteed-consistent link against the capnproto C++ runtime the nix shell's own python3Packages.pycapnp gets (see pyproject.toml's dependencies comment).

For that guarantee, do NOT use a --system-site-packages venv — tried and confirmed broken: this nix devShell injects click/pycapnp/PyYAML/ jsonschema via $PYTHONPATH, not into the base python3 interpreter's own site-packages (it's the bare interpreter derivation; each package is a separate /nix/store/... path added to $PYTHONPATH by the shell hook — confirmed via nix develop … --command sh -c 'echo $PYTHONPATH'). A --system-site-packages venv only inherits the base interpreter's own (empty) site-packages, so it silently can't see any of them — ModuleNotFoundError: No module named 'click' the moment you actually run it, even though the venv's own pip install step succeeds. Instead extract the wheel directly and bake the resolved $PYTHONPATH into a wrapper at install time (no venv/pip needed at all) — this is exactly what /xce-deploy-ha's deploy-ha.sh does for scripts/xceadmin, and it's proven to work with zero nix/network dependency at runtime:

mkdir -p /path/to/xceadmin-site
python3 -m zipfile -e /path/to/xceadmin-0.1.0-py3-none-any.whl /path/to/xceadmin-site
nix develop ./src --command bash -c '
  cat > /path/to/xceadmin << EOF
#!'"$(nix develop ./src --command which bash)"'
export PYTHONPATH="/path/to/xceadmin-site:$PYTHONPATH"
exec '"$(nix develop ./src --command which python3)"' -m xceadmin.cli "\$@"
EOF
  chmod +x /path/to/xceadmin
'

($PYTHONPATH must be expanded inside the nix develop --command shell, not the outer one, so the resolved nix store paths get baked into the wrapper literally — see deploy-ha.sh's own xceadmin-install block for the exact working pattern.)