From 91782e4089645d58f473b8f9db40cb2a9b631ad4 Mon Sep 17 00:00:00 2001 From: Rongsong Shen Date: Sat, 3 Oct 2026 13:07:37 +0000 Subject: [PATCH] docs: bug-1909..1912 fixed; controller-peers-env-fallback gap notes resolved Co-Authored-By: Claude Opus 5.5 (1M context) --- .../controller-peers-env-fallback/tasks.md | 1 + .wolf/buglog.json | 94 ++++++++++++++++++- .wolf/bugs/bug-1909.md | 15 +-- .wolf/bugs/bug-1910.md | 59 ++++++++++++ .wolf/bugs/bug-1911.md | 58 ++++++++++++ .wolf/bugs/bug-1912.md | 57 +++++++++++ 6 files changed, 272 insertions(+), 12 deletions(-) create mode 100644 .wolf/bugs/bug-1910.md create mode 100644 .wolf/bugs/bug-1911.md create mode 100644 .wolf/bugs/bug-1912.md diff --git a/.kiro/specs/controller-peers-env-fallback/tasks.md b/.kiro/specs/controller-peers-env-fallback/tasks.md index 3b82596c..f6070aba 100644 --- a/.kiro/specs/controller-peers-env-fallback/tasks.md +++ b/.kiro/specs/controller-peers-env-fallback/tasks.md @@ -141,3 +141,4 @@ - (3.2 → 3.3) The file-wide `shellcheck disable=SC2329` must stay (cases and helpers run via the `"$fn"` registry and `trap`); the SC2034 disable now covers only the `HARNESS_BIN` line — remove it once the harness cases use it. - (5.2) Environment-limited completion: the new `[scheduler-no-peers]` check passes in both scripts with the variable set and unset (RED reproduced: with the variable set and no `unset`, the scheduler took the HA path and the check failed). Neither script exits 0 here: both stop at the apiserver `go build` (GOPROXY=off, no Go module cache), and `test-version-startup-consistency.sh` also fails its scheduler commit-id check on any dirty tree — pre-existing, logged as bug-1909. A full pass of both scripts still needs a machine with the Go module cache and a clean tree (or a bug-1909 fix); carry this into 5.3. - (5.3) At HEAD 353b16dd (ASAN, `src/xce.build`): `xce-build.sh all --incremental` exit 0 — meson C tests 127/0, scheduler 16/0, Go unit tests ok, xceadmin 364 passed, no ASAN/LSan reports; regression script 33/33 (twice). Environment-limited like 5.2: both version scripts pass every scheduler check incl. `[scheduler-no-peers]` but exit 1 at the apiserver `go build` (GOPROXY=off), and `test-version-startup-consistency.sh` also hits bug-1909 (it asserts `--version` and the startup log "must never disagree", while the dirty-fingerprint spec keeps `-dirty` in the log — a conflict between two earlier specs, not this feature). +- (post-validation, ef6e562c) The 5.2/5.3 environment limits are resolved: the version scripts now build the apiserver with `-mod=vendor` (bug-1910) and accept the dirty-content-fingerprint pairing (bug-1909), so both exit 0 here on a dirty tree with `XCE_CONTROLLER_PEERS` set. Also fixed: the worker `-l` + `-h`/`--version` leak (bug-1911) and `xce_parse_peers()` whitespace handling to match xceadmin (bug-1912). Regression script now 35 cases. diff --git a/.wolf/buglog.json b/.wolf/buglog.json index 3718553d..3900a9e1 100644 --- a/.wolf/buglog.json +++ b/.wolf/buglog.json @@ -17012,9 +17012,9 @@ }, { "id": "bug-1909", - "status": "open", + "status": "fixed", "status_updated": "2026-10-03", - "status_note": "newly logged", + "status_note": "", "component": "tooling", "severity": "wrong-result", "timestamp": "2026-10-03T08:09:49Z", @@ -17022,8 +17022,8 @@ "occurrences": 1, "error_message": "test-version-startup-consistency.sh scheduler check fails on any dirty tree (fingerprint vs -dirty)\n\n**Observed:**\n```\nFAIL [scheduler]: commit id MISMATCH ('ac7a4385-f4637557' vs 'ac7a4385-dirty')\n```\n`src/tools/test-version-startup-consistency.sh` compares the scheduler's `--version` commit id with the one in its startup log line. On a dirty working tree `--version` reports a content fingerprint (`-`) while the startup log keeps `-dirty`, so the scheduler check fails.\n\n**Expected:** the script passes on a dirty tree. The dirty-fingerprint feature keeps the startup log at `-dirty` on purpose (its own regression script, `test-dirty-fingerprint-startup-log-unchanged.sh`, asserts that), so the consistency check should compare the two forms accordingly (e.g. compare the base sha, or accept `-dirty` in the log when `--version` carries a fingerprint).\n**Impact:** the script can only pass from a clean tree; any developer machine with uncommitted changes (here `.wolf/`, `.claude/`, `data/`) sees a false failure. Test-only; product binaries behave as designed.\n**Found by:** spec task (controller-peers-env-fallback task 5.2)", "file": "src/tools/test-version-startup-consistency.sh", - "root_cause": "TODO: required for fixed/partial", - "fix": "TODO: required for fixed/partial\n\nVerification:\n- Test: TODO: name + file; RED before the fix, GREEN after\n- Suites: TODO: suite results and build profile\n- Live: TODO: soak/deploy run, or n/a and why", + "root_cause": "`check_consistency()` in `src/tools/test-version-startup-consistency.sh` (written for build-time-versioning Req 2.2) requires the `--version` and startup-log commit ids to be byte-identical. The later dirty-content-fingerprint feature deliberately made `--version` show `-<8-hex fingerprint>` on a dirty tree while the startup log keeps `-dirty` (its requirements.md scope note and Req 3.2 call this a scoped exception), but this script was never updated, so it fails on any dirty tree.", + "fix": "`check_consistency()` now treats exactly that pairing as agreement: `--version` matching `^(.+)-[0-9a-f]{8}$` and the startup log equal to `-dirty` (the fingerprint is `sha256sum | cut -c1-8`, `xce-build.sh`). Anything else is still compared byte-for-byte. Self-test cases (e) (pairing accepted) and (f) (different sha, or a fingerprint against a clean log, rejected) added to `demonstrate_detection_capability()`; header, PASS line and final banner reworded.\n\nVerification:\n- Test: `demonstrate_detection_capability()` case (e) in `src/tools/test-version-startup-consistency.sh` — RED before the fix (extracted harness: `FAIL [DEMO-dirty-fingerprint-pairing]: commit id MISMATCH`), GREEN after; cases (a)-(c), (f) still reject.\n- Suites: `bash src/tools/test-version-startup-consistency.sh` exit 0 on a dirty tree (`[scheduler] --version fingerprint '66520e6b-a46c0a00' pairs with startup log '66520e6b-dirty'`), 2026-10-03; `xce-build.sh all --incremental` (asan) green.\n- Live: n/a (test-script defect)", "tags": [ "test-script", "version", @@ -17031,8 +17031,92 @@ "scheduler" ], "related_bugs": [], - "fix_commits": [], + "fix_commits": [ + "ef6e562c" + ], "record": ".wolf/bugs/bug-1909.md" + }, + { + "id": "bug-1910", + "status": "fixed", + "status_updated": "2026-10-03", + "status_note": "", + "component": "tooling", + "severity": "wrong-result", + "timestamp": "2026-10-03T12:14:03Z", + "last_seen": "2026-10-03T12:14:03Z", + "occurrences": 1, + "error_message": "version scripts' apiserver go build uses -mod=readonly and fails without a Go module cache\n\n**Observed:**\n```\nmodels/auth.go:4:2: module lookup disabled by GOPROXY=off\nutils/utils.go:18:2: module lookup disabled by GOPROXY=off\n...\nFATAL: command failed (line 383)\n```\nBoth `test-version-startup-consistency.sh` and `test-dirty-fingerprint-startup-log-unchanged.sh` build the apiserver with `GOPROXY=off go build -mod=readonly ...`. `-mod=readonly` makes Go ignore the committed `src/frontend/apiserver/vendor/` tree and resolve modules from the module cache, which this machine does not have, so the build fails and the scripts never reach their apiserver checks.\n\n**Expected:** the scripts build the apiserver the way the canonical meson build does (`GOFLAGS=-mod=vendor`, `src/frontend/apiserver/meson.build:89`), which needs no module cache and never modifies go.mod/go.sum (the concern -mod=readonly was added for, bug-1607).\n**Impact:** both scripts exit 1 on any machine without a populated Go module cache; their apiserver halves are never exercised. Test-only.\n**Found by:** spec task (controller-peers-env-fallback tasks 5.2/5.3)", + "file": "src/tools/test-version-startup-consistency.sh, src/tools/test-dirty-fingerprint-startup-log-unchanged.sh", + "root_cause": "Both scripts build the apiserver with `GOPROXY=off go build -mod=readonly`. `-mod=readonly` makes Go ignore the committed `src/frontend/apiserver/vendor/` tree and resolve modules from the module cache; the scripts were written on a machine whose cache was already populated, so on a machine without one every import fails with `module lookup disabled by GOPROXY=off`.", + "fix": "Use `-mod=vendor`, as the canonical meson build does (`GOFLAGS=-mod=vendor`, `src/frontend/apiserver/meson.build:89`). Vendor mode needs no module cache and never modifies go.mod/go.sum, so the bug-1607 no-repo-mutation property still holds. Header comments updated in both scripts.\n\nVerification:\n- Test: `bash src/tools/test-dirty-fingerprint-startup-log-unchanged.sh` — RED before (FATAL at the apiserver `go build`, 12x `module lookup disabled by GOPROXY=off`), GREEN after (exit 0, apiserver checks PASS); same for `test-version-startup-consistency.sh` (exit 0, `PASS [apiserver]`), 2026-10-03.\n- Suites: go.mod/go.sum unchanged (md5 and `git status src/frontend` checked by the reviewer); `xce-build.sh all --incremental` (asan) green.\n- Live: n/a (test-script defect)", + "tags": [ + "test-script", + "apiserver", + "go", + "vendor" + ], + "related_bugs": [ + "bug-1607", + "bug-1608", + "bug-1909" + ], + "fix_commits": [ + "ef6e562c" + ], + "record": ".wolf/bugs/bug-1910.md" + }, + { + "id": "bug-1911", + "status": "fixed", + "status_updated": "2026-10-03", + "status_note": "", + "component": "worker", + "severity": "leak", + "timestamp": "2026-10-03T12:14:06Z", + "last_seen": "2026-10-03T12:14:06Z", + "occurrences": 1, + "error_message": "worker leaks the -l log-dir string when -h or --version follows -l\n\n**Observed:**\n```\n$ xcecd_virtual -l -h # rc=1 under ASAN\n==222451==ERROR: LeakSanitizer: detected memory leaks\nDirect leak of 127 byte(s) in 1 object(s) allocated from:\n```\nSame with `--version`. `-l` stores `strdup(optarg)` in `logdir`; the `-h`/default and `--version` cases in the getopt loop (`src/worker/src/main.c` ~L433-465) `return 0` without freeing it.\n\n**Expected:** `-h` and `--version` exit 0 with no leak report, whatever other options precede them.\n**Impact:** under ASAN/LSan the worker's help/version exits 1 with a leak report when `-l` is given first; trivial in release builds (process exits). Pre-existing, found while reviewing controller-peers-env-fallback task 3.2.\n**Found by:** review (controller-peers-env-fallback task 3.2)", + "file": "src/worker/src/main.c", + "root_cause": "In `src/worker/src/main.c`'s getopt loop, `-l` stores `strdup(optarg)` in `logdir`, but the `--version` (`OPT_VERSION`) and `-h`/default cases `return 0` without freeing it. Under ASAN, LeakSanitizer reports the 127-byte block and turns the exit status into 1.", + "fix": "`FREEUP(logdir)` before both `return 0` statements. The other early returns after `-l` (peers parse/validation errors, mkdir failure) already free it.\n\nVerification:\n- Test: `case_worker_help_version_after_logdir_no_leak` in `src/tools/verify-controller-peers-env.sh` — RED before (`expected exit status 0, got 1`), GREEN after.\n- Suites: `xce-build.sh all --incremental` (asan): worker suite and all C tests 127/0, no ASAN/LSan reports; regression script 35/35, 2026-10-03.\n- Live: n/a", + "tags": [ + "asan", + "lsan", + "worker", + "cli" + ], + "related_bugs": [], + "fix_commits": [ + "ef6e562c" + ], + "record": ".wolf/bugs/bug-1911.md" + }, + { + "id": "bug-1912", + "status": "fixed", + "status_updated": "2026-10-03", + "status_note": "", + "component": "lib", + "severity": "wrong-result", + "timestamp": "2026-10-03T12:14:09Z", + "last_seen": "2026-10-03T12:14:09Z", + "occurrences": 1, + "error_message": "xce_parse_peers keeps spaces around entries; xceadmin trims them, so name matching differs\n\n**Observed:**\n```\n$ XCE_CONTROLLER_PEERS=\"a=localhost:1, b=localhost:2, c=localhost:3\" xce_controller --myname b ...\nxce_controller: XCE_CONTROLLER_PEERS (a=localhost:1, b=localhost:2, c=localhost:3) has no candidate named 'b'; pass --myname ...\n```\n`xce_parse_peers()` splits on `,` and keeps the surrounding whitespace, so the second candidate's name is `\" b\"`. xceadmin's `resolve_controllers()` strips whitespace around each entry, so the same value works there.\n\n**Expected:** spaces around entries are ignored, as in xceadmin: `\"a=h:1, b=h:2\"` parses as names `a` and `b`.\n**Impact:** a peers list written with spaces after commas (natural in a shell variable or config) makes the controller's identity match fail (1.6 refusal from the env var, or a listener refusal with explicit --peers) and gives names/hosts with stray spaces in every C component. Shared libxce parser: affects controller, scheduler, worker, harness, apiserver backend.\n**Found by:** audit (controller-peers-env-fallback feature validation)", + "file": "src/lib/cluster.c", + "root_cause": "`xce_parse_peers()` (`src/lib/cluster.c`) splits on `,` with `strtok_r` and uses each token as-is, so whitespace after a comma becomes part of the next candidate's name (or hostname), and a blank entry is a parse error. xceadmin's `resolve_controllers()` strips each entry and skips blanks, so the same value meant different things to the C components and xceadmin.", + "fix": "Trim leading/trailing `isspace()` characters from each token in place and skip tokens that are empty after trimming (they no longer use a `MAX_HA_CANDIDATES` slot); doc comment in `src/include/xce/cluster.h` updated. Previously valid inputs parse identically apart from losing stray outer whitespace.\n\nVerification:\n- Test: `test_parse_peers_trims_spaces_around_entries`, `test_parse_peers_trims_unnamed_entry`, `test_parse_peers_skips_blank_entries` (+ guards `test_parse_peers_all_blank_rejected`, `test_parse_peers_entry_without_port_rejected`) in `src/lib/testsuite/test_cluster.c` — RED before (`Expected 'b' Was ' b'`, `Expected 'localhost' Was ' localhost'`, `Expected 0 Was -1`), GREEN after; regression case `case_controller_env_spaced_list` RED → GREEN.\n- Suites: libxce 730/0; `xce-build.sh all --incremental` (asan): C tests 127/0, scheduler 16/0, xceadmin 364 passed, no ASAN; regression script 35/35, 2026-10-03.\n- Live: n/a", + "tags": [ + "peers", + "parser", + "xceadmin", + "consistency" + ], + "related_bugs": [], + "fix_commits": [ + "ef6e562c" + ], + "record": ".wolf/bugs/bug-1912.md" } ] } diff --git a/.wolf/bugs/bug-1909.md b/.wolf/bugs/bug-1909.md index a65e1118..932c09b1 100644 --- a/.wolf/bugs/bug-1909.md +++ b/.wolf/bugs/bug-1909.md @@ -1,8 +1,8 @@ --- id: "bug-1909" -status: "open" +status: "fixed" status_updated: "2026-10-03" -status_note: "newly logged" +status_note: "" component: "tooling" severity: "wrong-result" timestamp: "2026-10-03T08:09:49Z" @@ -11,6 +11,7 @@ occurrences: 1 files: ["src/tools/test-version-startup-consistency.sh"] tags: ["test-script", "version", "dirty-fingerprint", "scheduler"] related: [] +fix_commits: ["ef6e562c"] --- # bug-1909: test-version-startup-consistency.sh scheduler check fails on any dirty tree (fingerprint vs -dirty) @@ -41,15 +42,15 @@ FAIL [scheduler]: commit id MISMATCH ('ac7a4385-f4637557' vs 'ac7a4385-dirty') - Evidence: not kept (the script deletes its work dir on exit). Separately, the script's apiserver `go build` fails on this machine with "module lookup disabled by GOPROXY=off" (no Go module cache) — unrelated environment limitation. ## Root cause -TODO: required for fixed/partial +`check_consistency()` in `src/tools/test-version-startup-consistency.sh` (written for build-time-versioning Req 2.2) requires the `--version` and startup-log commit ids to be byte-identical. The later dirty-content-fingerprint feature deliberately made `--version` show `-<8-hex fingerprint>` on a dirty tree while the startup log keeps `-dirty` (its requirements.md scope note and Req 3.2 call this a scoped exception), but this script was never updated, so it fails on any dirty tree. ## Fix -TODO: required for fixed/partial +`check_consistency()` now treats exactly that pairing as agreement: `--version` matching `^(.+)-[0-9a-f]{8}$` and the startup log equal to `-dirty` (the fingerprint is `sha256sum | cut -c1-8`, `xce-build.sh`). Anything else is still compared byte-for-byte. Self-test cases (e) (pairing accepted) and (f) (different sha, or a fingerprint against a clean log, rejected) added to `demonstrate_detection_capability()`; header, PASS line and final banner reworded. ## Verification -- Test: TODO: name + file; RED before the fix, GREEN after -- Suites: TODO: suite results and build profile -- Live: TODO: soak/deploy run, or n/a and why +- Test: `demonstrate_detection_capability()` case (e) in `src/tools/test-version-startup-consistency.sh` — RED before the fix (extracted harness: `FAIL [DEMO-dirty-fingerprint-pairing]: commit id MISMATCH`), GREEN after; cases (a)-(c), (f) still reject. +- Suites: `bash src/tools/test-version-startup-consistency.sh` exit 0 on a dirty tree (`[scheduler] --version fingerprint '66520e6b-a46c0a00' pairs with startup log '66520e6b-dirty'`), 2026-10-03; `xce-build.sh all --incremental` (asan) green. +- Live: n/a (test-script defect) ## References - Related: none diff --git a/.wolf/bugs/bug-1910.md b/.wolf/bugs/bug-1910.md new file mode 100644 index 00000000..366aeaae --- /dev/null +++ b/.wolf/bugs/bug-1910.md @@ -0,0 +1,59 @@ +--- +id: "bug-1910" +status: "fixed" +status_updated: "2026-10-03" +status_note: "" +component: "tooling" +severity: "wrong-result" +timestamp: "2026-10-03T12:14:03Z" +last_seen: "2026-10-03T12:14:03Z" +occurrences: 1 +files: ["src/tools/test-version-startup-consistency.sh", "src/tools/test-dirty-fingerprint-startup-log-unchanged.sh"] +tags: ["test-script", "apiserver", "go", "vendor"] +related: ["bug-1607", "bug-1608", "bug-1909"] +fix_commits: ["ef6e562c"] +--- + +# bug-1910: version scripts' apiserver go build uses -mod=readonly and fails without a Go module cache + +## Description +**Observed:** +``` +models/auth.go:4:2: module lookup disabled by GOPROXY=off +utils/utils.go:18:2: module lookup disabled by GOPROXY=off +... +FATAL: command failed (line 383) +``` +Both `test-version-startup-consistency.sh` and `test-dirty-fingerprint-startup-log-unchanged.sh` build the apiserver with `GOPROXY=off go build -mod=readonly ...`. `-mod=readonly` makes Go ignore the committed `src/frontend/apiserver/vendor/` tree and resolve modules from the module cache, which this machine does not have, so the build fails and the scripts never reach their apiserver checks. + +**Expected:** the scripts build the apiserver the way the canonical meson build does (`GOFLAGS=-mod=vendor`, `src/frontend/apiserver/meson.build:89`), which needs no module cache and never modifies go.mod/go.sum (the concern -mod=readonly was added for, bug-1607). +**Impact:** both scripts exit 1 on any machine without a populated Go module cache; their apiserver halves are never exercised. Test-only. +**Found by:** spec task (controller-peers-env-fallback tasks 5.2/5.3) + +## Environment +- OS: no /etc/os-release; Docker container; NixOS kernel; kernel Linux 6.18.33 x86_64 +- libc: glibc 2.42-47 (nix devShell, /nix/store/wb6rhpznjfczwlwx23zmdrrw74bayxw4-glibc-2.42-47) +- Toolchain: gcc (GCC) 15.2.0, meson 1.9.1 +- Build: debug (the scripts' own lib/scheduler builds) + direct `go build` of the apiserver +- Code: scheduler-framework @ 66520e6b-dirty +- Deployment: n/a — local script builds, no cluster + +## Reproduction +1. On a machine with no Go module cache, run `bash src/tools/test-dirty-fingerprint-startup-log-unchanged.sh` (or `test-version-startup-consistency.sh`). +2. The apiserver step fails with `module lookup disabled by GOPROXY=off`. +- Frequency: deterministic on this machine, 2026-10-03 (every run during controller-peers-env-fallback 5.2/5.3) +- Evidence: not kept (scratchpad logs from the 5.3 run) + +## Root cause +Both scripts build the apiserver with `GOPROXY=off go build -mod=readonly`. `-mod=readonly` makes Go ignore the committed `src/frontend/apiserver/vendor/` tree and resolve modules from the module cache; the scripts were written on a machine whose cache was already populated, so on a machine without one every import fails with `module lookup disabled by GOPROXY=off`. + +## Fix +Use `-mod=vendor`, as the canonical meson build does (`GOFLAGS=-mod=vendor`, `src/frontend/apiserver/meson.build:89`). Vendor mode needs no module cache and never modifies go.mod/go.sum, so the bug-1607 no-repo-mutation property still holds. Header comments updated in both scripts. + +## Verification +- Test: `bash src/tools/test-dirty-fingerprint-startup-log-unchanged.sh` — RED before (FATAL at the apiserver `go build`, 12x `module lookup disabled by GOPROXY=off`), GREEN after (exit 0, apiserver checks PASS); same for `test-version-startup-consistency.sh` (exit 0, `PASS [apiserver]`), 2026-10-03. +- Suites: go.mod/go.sum unchanged (md5 and `git status src/frontend` checked by the reviewer); `xce-build.sh all --incremental` (asan) green. +- Live: n/a (test-script defect) + +## References +- Related: none diff --git a/.wolf/bugs/bug-1911.md b/.wolf/bugs/bug-1911.md new file mode 100644 index 00000000..ed15cf33 --- /dev/null +++ b/.wolf/bugs/bug-1911.md @@ -0,0 +1,58 @@ +--- +id: "bug-1911" +status: "fixed" +status_updated: "2026-10-03" +status_note: "" +component: "worker" +severity: "leak" +timestamp: "2026-10-03T12:14:06Z" +last_seen: "2026-10-03T12:14:06Z" +occurrences: 1 +files: ["src/worker/src/main.c"] +tags: ["asan", "lsan", "worker", "cli"] +related: [] +fix_commits: ["ef6e562c"] +--- + +# bug-1911: worker leaks the -l log-dir string when -h or --version follows -l + +## Description +**Observed:** +``` +$ xcecd_virtual -l -h # rc=1 under ASAN +==222451==ERROR: LeakSanitizer: detected memory leaks +Direct leak of 127 byte(s) in 1 object(s) allocated from: +``` +Same with `--version`. `-l` stores `strdup(optarg)` in `logdir`; the `-h`/default and `--version` cases in the getopt loop (`src/worker/src/main.c` ~L433-465) `return 0` without freeing it. + +**Expected:** `-h` and `--version` exit 0 with no leak report, whatever other options precede them. +**Impact:** under ASAN/LSan the worker's help/version exits 1 with a leak report when `-l` is given first; trivial in release builds (process exits). Pre-existing, found while reviewing controller-peers-env-fallback task 3.2. +**Found by:** review (controller-peers-env-fallback task 3.2) + +## Environment +- OS: no /etc/os-release; Docker container; NixOS kernel; kernel Linux 6.18.33 x86_64 +- libc: glibc 2.42-47 (interpreter /nix/store/wb6rhpznjfczwlwx23zmdrrw74bayxw4-glibc-2.42-47/lib/ld-linux-x86-64.so.2) +- Toolchain: gcc (GCC) 15.2.0, meson 1.9.1 +- Build: asan (xce.build) +- Code: scheduler-framework @ 66520e6b-dirty +- Deployment: n/a — single local process + +## Reproduction +1. `src/xce.build/worker/xcecd_virtual -l /tmp/x -h; echo rc=$?` (ASAN build) +2. LeakSanitizer reports a 127-byte direct leak and rc=1. Same with `--version`. +- Frequency: deterministic, 2026-10-03 +- Evidence: not kept + +## Root cause +In `src/worker/src/main.c`'s getopt loop, `-l` stores `strdup(optarg)` in `logdir`, but the `--version` (`OPT_VERSION`) and `-h`/default cases `return 0` without freeing it. Under ASAN, LeakSanitizer reports the 127-byte block and turns the exit status into 1. + +## Fix +`FREEUP(logdir)` before both `return 0` statements. The other early returns after `-l` (peers parse/validation errors, mkdir failure) already free it. + +## Verification +- Test: `case_worker_help_version_after_logdir_no_leak` in `src/tools/verify-controller-peers-env.sh` — RED before (`expected exit status 0, got 1`), GREEN after. +- Suites: `xce-build.sh all --incremental` (asan): worker suite and all C tests 127/0, no ASAN/LSan reports; regression script 35/35, 2026-10-03. +- Live: n/a + +## References +- Related: none diff --git a/.wolf/bugs/bug-1912.md b/.wolf/bugs/bug-1912.md new file mode 100644 index 00000000..5865cfdb --- /dev/null +++ b/.wolf/bugs/bug-1912.md @@ -0,0 +1,57 @@ +--- +id: "bug-1912" +status: "fixed" +status_updated: "2026-10-03" +status_note: "" +component: "lib" +severity: "wrong-result" +timestamp: "2026-10-03T12:14:09Z" +last_seen: "2026-10-03T12:14:09Z" +occurrences: 1 +files: ["src/lib/cluster.c"] +tags: ["peers", "parser", "xceadmin", "consistency"] +related: [] +fix_commits: ["ef6e562c"] +--- + +# bug-1912: xce_parse_peers keeps spaces around entries; xceadmin trims them, so name matching differs + +## Description +**Observed:** +``` +$ XCE_CONTROLLER_PEERS="a=localhost:1, b=localhost:2, c=localhost:3" xce_controller --myname b ... +xce_controller: XCE_CONTROLLER_PEERS (a=localhost:1, b=localhost:2, c=localhost:3) has no candidate named 'b'; pass --myname ... +``` +`xce_parse_peers()` splits on `,` and keeps the surrounding whitespace, so the second candidate's name is `" b"`. xceadmin's `resolve_controllers()` strips whitespace around each entry, so the same value works there. + +**Expected:** spaces around entries are ignored, as in xceadmin: `"a=h:1, b=h:2"` parses as names `a` and `b`. +**Impact:** a peers list written with spaces after commas (natural in a shell variable or config) makes the controller's identity match fail (1.6 refusal from the env var, or a listener refusal with explicit --peers) and gives names/hosts with stray spaces in every C component. Shared libxce parser: affects controller, scheduler, worker, harness, apiserver backend. +**Found by:** audit (controller-peers-env-fallback feature validation) + +## Environment +- OS: no /etc/os-release; Docker container; NixOS kernel; kernel Linux 6.18.33 x86_64 +- libc: glibc 2.42-47 (interpreter /nix/store/wb6rhpznjfczwlwx23zmdrrw74bayxw4-glibc-2.42-47/lib/ld-linux-x86-64.so.2) +- Toolchain: gcc (GCC) 15.2.0, meson 1.9.1 +- Build: asan (xce.build) +- Code: scheduler-framework @ 66520e6b-dirty +- Deployment: n/a — single local process + +## Reproduction +1. `XCE_CONTROLLER_PEERS="a=localhost:1, b=localhost:2, c=localhost:3" src/xce.build/controller/xce_controller --myname b --workdir --certdir --logfile /c.log` +2. stderr shows the refusal listing candidates `a, b, c` (leading spaces). +- Frequency: deterministic, 2026-10-03 +- Evidence: not kept + +## Root cause +`xce_parse_peers()` (`src/lib/cluster.c`) splits on `,` with `strtok_r` and uses each token as-is, so whitespace after a comma becomes part of the next candidate's name (or hostname), and a blank entry is a parse error. xceadmin's `resolve_controllers()` strips each entry and skips blanks, so the same value meant different things to the C components and xceadmin. + +## Fix +Trim leading/trailing `isspace()` characters from each token in place and skip tokens that are empty after trimming (they no longer use a `MAX_HA_CANDIDATES` slot); doc comment in `src/include/xce/cluster.h` updated. Previously valid inputs parse identically apart from losing stray outer whitespace. + +## Verification +- Test: `test_parse_peers_trims_spaces_around_entries`, `test_parse_peers_trims_unnamed_entry`, `test_parse_peers_skips_blank_entries` (+ guards `test_parse_peers_all_blank_rejected`, `test_parse_peers_entry_without_port_rejected`) in `src/lib/testsuite/test_cluster.c` — RED before (`Expected 'b' Was ' b'`, `Expected 'localhost' Was ' localhost'`, `Expected 0 Was -1`), GREEN after; regression case `case_controller_env_spaced_list` RED → GREEN. +- Suites: libxce 730/0; `xce-build.sh all --incremental` (asan): C tests 127/0, scheduler 16/0, xceadmin 364 passed, no ASAN; regression script 35/35, 2026-10-03. +- Live: n/a + +## References +- Related: none