Skip to content

Commit 6d80c39

Browse files
committed
fix(config): offline mode skips the first-use sandbox bootstrap
--offline promises never to touch the network. In a home that had never been used, load_or_init cloned the package index through `xlings self init` and installed ninja and patchelf through `xlings install` before any command ran: measured, 26 s and 126 MB under MCPP_OFFLINE=1. The three network-bound steps are now skipped under offline mode, the skip is announced once per process, and the completion markers are still written from what is on disk, so a bootstrapped home loses nothing. The commands that need the tools report their absence through check_base_init. e2e 604 asserts the fresh-home behaviour and, as its control, that a bootstrapped home announces nothing.
1 parent 0f84867 commit 6d80c39

5 files changed

Lines changed: 85 additions & 5 deletions

File tree

.agents/docs/2026-09-05-multi-device-implementation-plan.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,6 @@
133133
| 2026-09-05 | T2.4 完成 | `exclusive` 是列表不是布尔:一个包可提供多项能力而只有部分独占。schema 警告而非报错,因为绑定期那一处才是执行者 |
134134
| 2026-09-05 | ⚠️ 本机 shim 被 #582 剪掉一次 | 25 次 `xlings install``mcpp` 等 7 个裸名 shim 消失,store 完好。重装即恢复 —— 又一次受控复现,补进 issue |
135135
| 2026-09-05 | `libcublas` 暂不拆 static | 上游一个归档同时含 shared 与 static,拆分需要重打包并 re-host,与「不 re-host」冲突;先按上游形态发,拆分单列 |
136+
| 2026-09-05 | e2e 317 的等待窗从 2s 放宽到 5s | 到达「五次短失败」下界最少要 1.25s(4×250ms 重启延迟 + 5×50ms 轮询),2s 窗只给每次 spawn 留 150ms;main 上 macOS **连续两次**在此失败而本分支同码两次通过 —— 判据由 runner 负载决定。5s 窗留 750ms |
137+
| 2026-09-05 | e2e 602 声明 `requires: unix-shell`,并以 `MCPP_OFFLINE=1` 运行 | doctor 在 Windows 上整段不产出(载荷只有 linux 构建;Windows 工具包的上界是 `_MSC_VER` 区间,报告尚未读它);隔离 home 下 doctor 会把整套引导 + 工具链装进临时目录:实测 229s / 1.4 GB |
138+
| 2026-09-05 | ⚠️ 核心改动:`--offline` 下跳过首次沙箱引导 | `load_or_init` 在空 home 里克隆索引、经 `xlings install` 装 ninja/patchelf,全部走网络,违反 `--offline`「绝不碰网络」的承诺。实测 offline 空 home 26s / 126 MB → 0.3s;e2e 604 带对照(已引导的 home 不提示);文档中英各补一句 |

docs/00-getting-started.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,4 +196,4 @@ For the differences between the four modes and their artifact layouts, see [02
196196
default/sole template, or run `mcpp new --list-templates ocornut.imgui@1.92.8`).
197197
- Explaining default decisions: `mcpp why [toolchain|runtime|deps]`; host capability checkup: `mcpp self doctor`;
198198
machine-readable resolution manifest: the build artifact `target/<triple>/<fp>/resolution.json`.
199-
- Offline operation: `mcpp --offline` or `MCPP_OFFLINE=1` prevents index refreshes, downloads, and toolchain installation.
199+
- Offline operation: `mcpp --offline` or `MCPP_OFFLINE=1` prevents index refreshes, downloads, and toolchain installation. In a home that has never been used it also skips the first-use sandbox bootstrap (index clone, ninja, patchelf), announces the skip once, and leaves the home un-bootstrapped; commands that need those tools report it.

docs/zh/00-getting-started.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,4 +183,4 @@ mcpp pack --mode self-contained # 打包 loader、libc 与依赖
183183
`mcpp new --list-templates ocornut.imgui@1.92.8`)。
184184
- 解释默认决策:`mcpp why [toolchain|runtime|deps]`;主机能力体检:`mcpp self doctor`;
185185
机器可读解析清单:构建产物 `target/<triple>/<fp>/resolution.json`
186-
- 离线运行:`mcpp --offline``MCPP_OFFLINE=1` 可阻止索引刷新、下载和工具链安装。
186+
- 离线运行:`mcpp --offline``MCPP_OFFLINE=1` 可阻止索引刷新、下载和工具链安装。在从未使用过的 home 中,它同时跳过首次使用时的沙箱引导(索引克隆、ninja、patchelf),只提示一次,并让该 home 保持未引导状态;需要这些工具的命令会自行报告。

src/config.cppm

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -625,13 +625,40 @@ std::expected<GlobalConfig, ConfigError> load_or_init(
625625
// TODO(xlings-upstream): collapse into a single
626626
// `xlings sandbox bootstrap --home <X>` once that command exists
627627
// upstream (see docs/short-term-vs-long-track plan).
628+
//
629+
// NOT UNDER --offline. Each of the three steps below reaches the
630+
// network when the sandbox is fresh: `xlings self init` clones the
631+
// package index, and patchelf and ninja arrive through `xlings
632+
// install`. `--offline` promises never to touch the network, and an
633+
// empty home is exactly where that promise was being broken --
634+
// measured: `MCPP_OFFLINE=1 mcpp self doctor` in a fresh home fetched
635+
// the index and 126 MB of tools before running its first check.
636+
//
637+
// A bootstrapped home loses nothing: every step is a no-op once its
638+
// artefact exists, and the completion markers below are still written
639+
// from what is on disk. A fresh home stays un-bootstrapped, and the
640+
// commands that need the tools say so through check_base_init().
641+
const bool bootstrapAllowed = !mcpp::platform::env::offline_mode();
628642
ensure_sandbox_xlings_binary(cfg, quiet);
629-
ensure_sandbox_init(cfg, quiet);
643+
if (bootstrapAllowed) {
644+
ensure_sandbox_init(cfg, quiet);
645+
} else if (!quiet) {
646+
// Once per process: the doctor loads the configuration more than once,
647+
// and the same sentence twice reads as two events.
648+
static bool announced = false;
649+
auto marker = mcpp::xlings::paths::sandbox_init_marker(make_xlings_env(cfg));
650+
if (!announced && !std::filesystem::exists(marker)) {
651+
announced = true;
652+
print_status("Skipping",
653+
"sandbox bootstrap (offline mode; run without --offline to bootstrap)");
654+
}
655+
}
630656
{
631657
auto bsEnv = make_xlings_env(cfg);
632658
#if !defined(__APPLE__) && !defined(_WIN32)
633659
// patchelf is ELF-only; macOS uses Mach-O and Windows uses PE.
634-
ensure_sandbox_patchelf(cfg, quiet, onBootstrapProgress);
660+
if (bootstrapAllowed)
661+
ensure_sandbox_patchelf(cfg, quiet, onBootstrapProgress);
635662
// Only mark complete if the actual binary exists (not just the dir).
636663
{
637664
auto pBin = mcpp::xlings::paths::xim_tool(bsEnv, "patchelf",
@@ -640,7 +667,8 @@ std::expected<GlobalConfig, ConfigError> load_or_init(
640667
mcpp::fallback::mark_install_complete(pBin.parent_path().parent_path());
641668
}
642669
#endif
643-
ensure_sandbox_ninja(cfg, quiet, onBootstrapProgress);
670+
if (bootstrapAllowed)
671+
ensure_sandbox_ninja(cfg, quiet, onBootstrapProgress);
644672
{
645673
auto nRoot = mcpp::xlings::paths::xim_tool_root(bsEnv, "ninja");
646674
auto ninja_name = std::string("ninja") + std::string(mcpp::platform::exe_suffix);
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#!/usr/bin/env bash
2+
# `--offline` promises never to touch the network, and a fresh home is where
3+
# that promise was being broken: the sandbox bootstrap that `load_or_init` runs
4+
# on first use clones the package index and installs ninja and patchelf through
5+
# `xlings install`. Measured before the gate, `MCPP_OFFLINE=1 mcpp self doctor`
6+
# in an empty home spent 26 seconds and wrote 126 MB before its first check.
7+
#
8+
# Under offline mode the three network-bound steps are skipped, once, visibly,
9+
# and the command goes on to its remaining checks. A home that is already
10+
# bootstrapped says nothing, because there is nothing to skip.
11+
set -e
12+
13+
TMP=$(mktemp -d)
14+
trap "rm -rf $TMP" EXIT
15+
16+
# ── A fresh home under offline mode is left un-bootstrapped ─────────────
17+
out="$TMP/fresh.log"
18+
MCPP_HOME="$TMP/home" MCPP_OFFLINE=1 "$MCPP" self doctor > "$out" 2>&1 || true
19+
20+
n=$(grep -c "Skipping sandbox bootstrap (offline mode" "$out" || true)
21+
[[ "$n" -eq 1 ]] || {
22+
cat "$out"; echo "FAIL: expected the skip to be announced once, saw $n"; exit 1; }
23+
24+
# The index clone is the first network step and the largest; its absence is
25+
# the structural evidence that nothing was fetched.
26+
[[ ! -e "$TMP/home/registry/data/xim-pkgindex" ]] || {
27+
echo "FAIL: the package index was cloned under offline mode"; exit 1; }
28+
[[ ! -e "$TMP/home/registry/subos/default/.xlings.json" ]] || {
29+
echo "FAIL: the sandbox was initialised under offline mode"; exit 1; }
30+
31+
# Skipping the bootstrap must not end the command: the checks after the
32+
# registry one still run.
33+
grep -q "Checking build policy" "$out" || {
34+
cat "$out"; echo "FAIL: the doctor stopped at the registry check"; exit 1; }
35+
echo "PASS: a fresh home under offline mode is left un-bootstrapped, audibly"
36+
37+
# ── Control: a bootstrapped home has nothing to skip and says nothing ───
38+
#
39+
# Without this, a doctor that printed the sentence on every offline run would
40+
# pass the count above. The e2e runner's own home is bootstrapped by the time
41+
# this script runs.
42+
out2="$TMP/warm.log"
43+
MCPP_OFFLINE=1 "$MCPP" self doctor > "$out2" 2>&1 || true
44+
if grep -q "Skipping sandbox bootstrap" "$out2"; then
45+
cat "$out2"; echo "FAIL: a bootstrapped home announced a skip"; exit 1
46+
fi
47+
echo "PASS: a bootstrapped home under offline mode announces nothing"
48+
49+
echo "PASS: offline skips the sandbox bootstrap"

0 commit comments

Comments
 (0)