Commit Graph
3 Commits
Author SHA1 Message Date
retoorandClaude Sonnet 5 c414a27e44 Fix CI's actual root cause: a set -e scripting bug, not a code bug
CI / build-and-test (push) Successful in 41s
The reported failure -- "Build and run under ThreadSanitizer" exiting
with code 66 -- traced back to a real bug in .gitea/workflows/ci.yml
itself, confirmed by direct reproduction, not just theorized:

1. Root cause: this step's default shell runs under `set -e`. The
   previous version assigned OUT via a bare, unwrapped
   `OUT=$("./binary" 2>&1)` -- under `-e`, that command's own nonzero
   exit status aborts the whole script immediately, *before* the very
   next line (even `RC=$?`) ever runs. Confirmed with a two-line
   reproduction: `OUT=$(false); echo "after"` under `set -e` never
   prints "after". This meant none of the step's careful classification
   logic (distinguishing the known TSan-can't-start-here limitation from
   a real finding) ever executed -- the first binary to hit the FATAL/
   exit-66 case (all of them, on this runner) killed the step outright
   with that raw exit code, exactly the outcome the classification logic
   exists to prevent. Fixed by wrapping every such invocation in
   `if cmd; then RC=0; else RC=$?; fi`, which bash's `-e` rules exempt
   from triggering an abort -- verified directly under `bash -eo
   pipefail` (matching Gitea Actions' actual shell), not assumed correct.

2. Fixing (1) surfaced a second bug: capturing potentially unbounded
   output into a bash variable via `OUT=$(cmd)` is not just slow, it
   reproduced as a several-hundred-megabyte shell string when the
   DEADLYSIGNAL flake's worse, unbounded-repeating-loop form hit during
   this fix's own testing -- and caused the classification logic to
   misbehave at that scale (a real failure was misreported with "exit
   0"). Fixed in both the ASan/UBSan and TSan steps by redirecting
   output to a file and reading back only a bounded 64 KiB prefix for
   classification and logging, never loading the whole thing into a
   shell variable.

3. While repeatedly reproducing the TSan flake locally to verify (1) and
   (2), found a second, previously undocumented variant of the same
   underlying "TSan can't start on this runner" issue: instead of
   printing FATAL: ThreadSanitizer: unexpected memory mapping and
   exiting 66, TSan's broken startup occasionally segfaults outright.
   Confirmed this is the same environmental cause, not a bug in any
   specific test file, by hitting three different, unrelated binaries
   (test_journal_failure in one run, test_crash_consistency and test_dir
   together in another) across repeated full-suite runs -- a real bug
   in one file's code would not migrate to different files at random.
   The TSan step's classification now recognizes this variant too
   (a log containing only timeout's own "dumped core" notice and nothing
   else -- no program output, no real WARNING/SUMMARY ThreadSanitizer
   race report).

4. The ASan/UBSan step's retry budget was bumped from 3 to 5 after
   observing a real 3-in-a-row flake exhaustion in practice during this
   same verification work -- this sandbox's actual flake rate is
   meaningfully higher than the "roughly 1 in 5-10" CONTRIBUTING.md
   documents, and 3 retries turned out not to be a big enough margin.

5. Separately, the -Wunused-result warning on tests/test_crash_consistency.c's
   write() call: the (void) cast that silenced it locally did not silence
   it on the Gitea runner's gcc -- reproduced clean locally with the
   exact same compiler flags, confirming this is a real toolchain
   version/config difference, not a local misconfiguration. void-cast
   suppression of warn_unused_result is documented as unreliable across
   gcc configurations; fixed with an actual conditional branch on the
   return value instead, which every gcc/clang version used so far
   honors.

Every fix here was verified by direct reproduction under `bash -eo
pipefail` locally (matching Gitea Actions' actual shell invocation), not
reasoned about and assumed correct: the original -e bug was reproduced
and fixed, the TSan step was re-run 10 full-suite times (80 individual
binary executions) with both flake variants recurring and both correctly
classified as warnings rather than errors, and the ASan/UBSan step was
re-run 10 full-suite times with the 5-retry budget with zero false
failures. Also verified with a clean make all + make test locally
(zero warnings, all 8 binaries pass).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UqJpkdJ6Njnt1pw3CbghzB
2026-09-14 20:55:54 +00:00
retoorandClaude Sonnet 5 0c0ae4f747 CI: don't hard-fail on the confirmed TSan-can't-start-here runner limitation
CI / build-and-test (push) Failing after 23s
The real Gitea Actions run on this project's own registered runner just
failed exactly as CONTRIBUTING.md already anticipated it might:
FATAL: ThreadSanitizer: unexpected memory mapping, the same signature
already documented as a sandbox/container seccomp restriction blocking
personality(ADDR_NO_RANDOMIZE), which TSan needs to start at all. Build,
test suite, and ASan/UBSan all passed -- only the TSan step failed, on an
environment issue, not a code issue.

Fixed the CI step itself rather than just noting the failure: it now
classifies each binary's TSan run as PASS, a known flake (that exact
FATAL signature and nothing indicating an actual race was found), or a
real failure (anything else -- a genuine data race, a crash, any other
error). Only a real failure fails the build. Verified the classification
logic locally against four cases (a synthetic real race report, a plain
assertion failure, the known flake signature alone, and a clean pass) --
each classified correctly -- and against this sandbox's own six test
binaries, all six of which hit the real flake (this sandbox has never
been able to run TSan either) and correctly did not fail the build.

This does NOT mean TSan verification is happening in CI -- it means CI no
longer conflates "TSan couldn't start" with "the build is broken."
Updated CONTRIBUTING.md and CLAUDE.md from "whether the runner can execute
TSan is unverified" (a hedge) to the now-confirmed fact that it can't, and
corrected an overclaim in README.md that the suite is "regularly run under
ThreadSanitizer" -- as far as this project has been able to confirm, TSan
has not actually completed a run in any environment it's been built in
yet, local sandbox or CI. ASan/UBSan remain the real, run, load-bearing
sanitizer coverage.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UqJpkdJ6Njnt1pw3CbghzB
2026-09-14 11:53:08 +00:00
retoorandClaude Sonnet 5 21bcb4640f Move CI to Gitea Actions; this project is hosted on Gitea, not GitHub
CI / build-and-test (push) Failing after 23s
Per explicit direction: this repository is not going on GitHub. Moved
.github/workflows/ci.yml to .gitea/workflows/ci.yml (Gitea Actions'
convention) and updated every doc that referenced the old path or assumed
GitHub-specific features:

- .gitea/workflows/ci.yml: added a header comment on the two things that
  are genuinely Gitea-specific and instance-dependent, not just a renamed
  file -- `runs-on: ubuntu-latest` must match a label the actual
  registered Gitea runner advertises (there is no GitHub-hosted-runner
  equivalent, this is self-hosted), and `actions/checkout@v4` resolves
  against whatever action source that runner is configured with.
- CONTRIBUTING.md: corrected a claim that no longer holds -- it previously
  said CI "runs on a normal, unrestricted GitHub Actions VM where TSan is
  expected to work"; since this is actually a self-hosted Gitea runner
  whose environment isn't controlled by this repo, that assumption isn't
  something this repo can vouch for, so the text now says so rather than
  carrying the old (GitHub-shaped) assumption forward silently.
- SECURITY.md: removed a claim this project can't back up (that "private
  security advisories" are available once hosted -- that's a GitHub
  feature this repo never had access to); reporting is by direct email to
  the maintainer only.
- README.md: fixed a real gap while in here -- the "Security" section
  never actually linked to SECURITY.md despite it existing since the
  previous commit.
- CLAUDE.md, CHANGELOG.md: updated path references; CLAUDE.md's
  self-evaluation section (a historical record of an audit finding) keeps
  the old .github path where it describes what was literally true at that
  time, with a note explaining the rename, rather than rewriting history.

Verified: clean make all + make test, all 6 binaries pass.

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