The buglog was one 27K-line JSON file whose fields covered only symptom,
root cause and fix, as free text: 2% of entries named the code version a
bug was found on, 12% the build profile, 36% a way to reproduce, and 58% of
the file was OpenWolf hook auto-detections ("Missing guard clause",
"Function not marked async" on .md files).
- .claude/skills/buglog/bug-record.md: the record standard. Eight
sections (Summary as the title, Description, Environment, Reproduction;
Root cause, Fix, Verification, References once fixed), each with the
content it must contain -- e.g. Environment needs the OS, the libc the
failing binary loads (from its ELF interpreter: glibc 2.42 in the nix
shell vs 2.40 on the host), toolchain, build profile and commit.
- .wolf/bugs/<id>.md: 500 records migrated from buglog.json; every field
verified to round-trip. They carry legacy: true and an explicit "not
recorded" where the old log had no such field; component derived from
file paths, severity from keywords (else unknown), fix_commits from
hashes in status_note/fix that git resolves (91 of 463 fixed).
- .wolf/buglog-archive.json: the 710 hook auto-detections, verbatim.
- .wolf/buglog.json is now an index generated from the records, keeping
the fields OpenWolf's hooks and `openwolf bug search` read.
- scripts/buglog.py rewritten: new (template + Environment collected from
the machine and --binary), check, status (refuses a status the record
doesn't support; fixed needs Root cause, Fix, Verification and
--commit hashes git resolves), note, recur, set, show, list, triage,
env, index. 29 unittest cases in scripts/tests/test_buglog.py.
- scripts/git-hooks/pre-commit runs `check --staged`: incomplete staged
records, a legacy record hand-changed to fixed without the fix-time
sections, a new record claiming legacy: true, or a stale index refuse
the commit.
- scripts/gitea-sync.py reads the records: issue body = the record's
sections plus linked fix commits (origin remote, GITEA_COMMIT_REPO to
override), and a body hash so an issue's text is refreshed when the
record changes.
- bug-1866 rewritten to the full standard as the worked example.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
5.2 KiB
| id | status | status_updated | status_note | component | severity | timestamp | last_seen | occurrences | files | tags | related | legacy | gitea_issue | gitea_synced_status | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| bug-1449 | fixed | 2026-09-24 | unknown | unknown | 2026-07-18T09:56:00.000Z | 2026-09-19T09:00:00Z | 1 |
|
|
true | 34 | fixed |
bug-1449: [xce-test-ha] FAIL: round 1: no new primary elected after kill (no serving primary at term>2)…
Description
[xce-test-ha] FAIL: round 1: no new primary elected after kill (no serving primary at term>2) -- hit once during ha-elect-ctx-lifecycle kiro-validate-impl soak, did NOT reproduce on immediate retry (10/10 clean)
Environment
Not recorded (migrated from buglog.json).
Reproduction
Not recorded (migrated from buglog.json).
Root cause
Test-harness timing race, not a code defect. Root-caused from the controller logs (not assumed): cold-start election was unusually slow this run -- ctrl-1 only reached CTRL_PRIMARY (term=2) ~10.1s after process start (raft_cb_become_leader). Immediately on becoming primary it began its config-push handshake to both standbys (on_standby_connected: 'outbound standby connection to peer[N] established, ctrlhello sent', same ~10.1s timestamp). The driver's round-1 kill fired only ~1.7s later (its budget is 'submit 3 jobs + verify + walcompare' after primary is detected, no floor on how fast that completes) -- before either standby had received and applied the pushed config. Both standbys were still at config tag=0 when ctrl-1 died, so ha_on_primary_ctrl_fd_rdhup correctly refused to start PreVote/RDHUP re-election per ha-config-bootstrap's own Req 2.8 guard ('no local config (tag=0, n_peers=3) -- voter only, not starting PreVote/RDHUP re-election; awaiting configured primary'), leaving the cluster with no primary and no candidate. This is the guard working exactly as designed against an unconfigured standby self-electing -- the actual gap is that run-ha-soak.sh's sequential mode has no wait for initial 3-of-3 config convergence before round 1's first kill (unlike its wait for FULL reconvergence between later rounds). Sequential mode's round-1 timing has apparently never lined up this unluckily in any of the many other Group-1 kiro-validate-impl soaks run earlier the same session (ha-wal-replication, ha-heartbeat-committed-propagation, ha-prevote, raft-sim-conformance, libxce-failover, ha-entity-snapshots, ha-commit-durability, ha-term-safety, controller-status, job-array, job-array-single-wal-record) -- all of which use the same driver script unmodified and ran clean.
Fix
FIXED (.claude/skills/xce-test-ha/run-ha-soak.sh). Added a round-1-only config-convergence gate that mirrors wait_for_full_convergence's exact polling style (same alive-count guard via pgrep -c -f "$(run_pattern)", same STABLE_TICKS=2-consecutive-converged-probes, same sleep 2 backoff, same ~90s/30-iteration bound, same log()/fail() convention) but gates on a NEW signal, cluster_config_converged (all 3 nodes report a non-zero configVersion via a new node_config_tag() single-node ctrlstatus client probe, mirroring node_probe's technique), instead of the existing cluster_committed_converged (committed-prefix catch-up) -- the two convergence signals are independent, and only the config-tag one covers the ha-config-bootstrap Req 2.8 voter-only guard. Added functions node_config_tag() / cluster_config_converged() / wait_for_initial_config_convergence() right after wait_for_full_convergence (~line 556). Call site inserted right after the chaotic-mode branch's closing fi and before the 'Steps 7-10 looped' round loop (~line 1897), so it applies only to the sequential/default path (chaotic mode intentionally tolerates this race by design -- 'no new primary' is a COUNTED outcome there, never a hard fail, so it was correctly left untouched). Diff: +77 lines, 0 lines removed -- no C source touched. VERIFIED: 3 back-to-back --sequential 6-round soaks on a genuine ext4 path (/home/claude/tmp, confirmed via mount/df -T, not overlayfs -- bug-924), all 3 PASS (6 rounds/24 jobs/zero loss/all 3 nodes led, identical verdict each run), and the new 'waiting for 3-of-3 initial config convergence before round 1' / 'confirmed' log lines appeared correctly ahead of every round 1. Cross-checked the actual cold-start timing from controller logs across all 3 runs to size the margin: election (raft_cb_become_leader -> CTRL_PRIMARY) took 10.3-11.7s after process start each run, and the config-push (handle_repl_config_push on both standbys) landed only 40-46ms after election every time -- i.e. the actual condition resolves in well under 100ms, while the new gate's minimum dwell is ~4s (2 ticks x 2s) and its ceiling is ~90s, giving roughly 2 orders of magnitude of margin over the observed timing, not just enough for these 3 runs' luck.
Verification
Not recorded (migrated from buglog.json).
References
- Gitea: #34