myxce/.claude/skills/xce-build/SKILL.md
Rongsong Shen 9f539583d3
docs: sync skills and steering docs with bug-1562..1566 findings
- .claude/skills/xce-build/SKILL.md: --incremental flag,
  xceadmin wheel building, corrected the standalone-install
  guidance (the earlier --system-site-packages venv approach was
  wrong -- nix devShell packages are $PYTHONPATH entries, not
  merged into site-packages).
- .claude/skills/xce-deploy-ha/SKILL.md: $BASH_BIN resolution,
  worker/scheduler startup, xceadmin usage against a deployment,
  troubleshooting rows for the newly-fixed bugs.
- .kiro/steering/ha-async-io.md: bug-1562's standby liveness
  watchdog re-arm invariant.
- .kiro/steering/persistence.md: bug-1563 (fsync backoff) and
  bug-1564 (persist_flush durable-advance callback).
- .kiro/steering/build-deployment.md: xceadmin wheel docs,
  bug-1565's --incremental flag, deploy dir tree updates.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 10:21:25 +00:00

10 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.

Always dispatch the build via the Agent tool

Do not run the nix develop … xce-build.sh command inline in the current session's Bash tool. Instead, on every invocation of this skill, launch a subagent with the Agent tool, giving it a fully self-contained prompt: repo working directory, the exact resolved command (component/profile/build_dir already substituted — do not make the subagent re-parse ARGUMENTS itself), what the command does, and what to report back (build success/failure, compiler errors, the meson test Ok/Fail summary line, and confirmation the expected binaries were freshly rebuilt with fresh mtimes). Wait for it (do not background it — the build is synchronous and finishes in well under this session's turn budget) and relay its report.

This is deliberately a plain Agent-tool dispatch, not context: fork — that frontmatter field reproducibly failed to thread a non-empty ARGUMENTS payload into the forked subagent (see above), whereas an explicit, self-contained Agent prompt has no such dependency and is proven to work.

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.)