fix: get every CI job green (norm debt, ft_malloc link, oracle skew) - #1
Merged
Conversation
The dirty star ran git status synchronously and blocked the prompt for the whole scan: cd into a repo where the scan takes seconds froze the first prompt (and every 3s TTL refresh froze again). The check now waits at most 60ms, and only for a freshly entered root — normal repos keep their exact immediate star — then finishes in the background on a non-blocking, cloexec pipe harvested by a later render. Completion is detected by pipe EOF, so the bg-job waitpid(-1) reaper stealing the zombie is harmless; a root change abandons the in-flight child; a signal-killed child voids its sample instead of caching a false clean; slow repos stretch the TTL 3s->30s so git is not hammered. Measured (pty, git shim sleeping 2s): cd->prompt 2.034s -> 0.077s, TTL refresh 2.026s -> 0.008s. New pty gate: make git-prompt-test.
norminette 3.3.59 (the version CI pins) reported 120 errors that had accumulated invisibly — make norm no-ops on hosts without norminette while the CI gate enforces it. Behavior-preserving mechanical fixes: - 33 over-25-line functions split into cohesive static helpers - 18 over-5-function files rebalanced, siblings created where a family was full (alias_scan6, assignment_to_env2, expand_param_format7, parse_arena3, reparse_assign_words2, set_opts3, env_assoc3, token_old.h) - assoc_next's five pointer out-params packed into a t_assoc_it iterator (env.h), fixing TOO_MANY_ARGS at every call site - PARENA_ROUND macro (forbidden macro-func) became the shared parena_round() — LTO inlines it back on optimized builds - ternaries, >5-local functions, >4-arg signatures, preproc constant expressions (SHOPT bits, parena sizes), header continuation indents, tab/space and alignment fixes Every extraction replays the original statements in the original order; the golden suite (3051 cases vs bash --posix) stays green.
bash fills SHELL with the current user's passwd login shell when the variable is entirely absent from the environment (an inherited empty SHELL= stays empty) and keeps it un-exported. hellish left it unset, which diverges in scrubbed environments — docker containers and CI runners — where the suite's echo $SHELL cases then fail against the bash oracle.
The forked external-command child exited through exit() after a failed execve, running atexit handlers — under the ASan/LSan debug build that includes the leak check, scanning the half-execution state copied at fork. Whether some argv string looks reachable there depends on stale stack-frame contents, so unrelated layout changes made the check fire randomly and its error exit clobbered the child's 127/126 with the sanitizer status — flipping golden-suite cases. _exit skips atexit in a process whose address space is about to die anyway; the child was already forbidden from freeing parent state (see exit_clean rules).
libft declares its nested ft_malloc submodule update=none, so git submodule update --init --recursive skips it: a fresh clone (and CI's checkout) silently built the libc fallback, then died at link on the force-bound malloc_live_bytes. The libft rule now materialises the submodule (--checkout overrides the none policy) whenever SAFE=0 needs it and it is absent, with a clear failure message when offline.
The Tests job diffed hellish against the runner's distro bash, and ubuntu-24.04 ships 5.2 — bash 5.2 vs 5.3 disagree on ~30 golden cases, which surfaced as phantom hellish failures. Build the 5.3.9 oracle once from GNU sources (cached by runner+arch) and put it first in PATH. Also upload suite.log as an artifact on failure so the diverging cases are visible without re-running.
Picks up two libft fixes: slab_free now proves chunk ownership exactly instead of guessing via span+magic (a foreign heap pointer landing in a span gap could be silently dropped or corrupt a freelist), and the build's dotfile sweep no longer moves .gitmodules into the build tree where fclean deleted it — which is what kept un-declaring the ft_malloc submodule on every deep rebuild.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Gets every CI job green. Three independent root causes, plus two latent bugs found on the way:
Why CI was red
make normno-ops on hosts without norminette while CI enforces it. Fixed by a behavior-preserving refactor sweep (function splits, file rebalancing, ternary/arg-count/local-count fixes, header cleanups). Full suite stays 3051/3051.update = none, sosubmodules: recursivenever fetches it; libft silently built its libc fallback and the link died on the force-boundmalloc_live_bytes. The Makefile now materialises ft_malloc (--checkout) whenever SAFE=0 needs it. Root cause was compounded by a libft bug where every build moved.gitmodulesinto the build tree and fclean deleted it — fixed in libft, pointer bumped here.echo $SHELLin scrubbed envs) exposed a real gap: hellish now synthesises$SHELLfrom passwd like bash (absent-only, un-exported).Latent bugs fixed along the way
execve, the child exited viaexit(), letting the leak check scan half-execution state inherited atfork(). Layout-dependent, and its error status clobbered 126/127. The child now_exits.slab_freeguessed ownership by span+magic: a foreign heap pointer in a chunk gap could be silently dropped (leak) or corrupt a freelist. Now proves ownership per chunk viaslab_get_block_at_ptr.git status—cdinto huge repos went from multi-second prompt stalls to instant (new pty gate:make git-prompt-test).Verification
make test: 3051/3051 vs bash --posix (host, bash 5.3.9)verify_alloc.sh: both heaps, output parity, 0 leaks / flat live-bytessrc incs: 0 errorsmakeandmake OPT=1both build; pty gates (git-prompt-test,hist-test,readline-test,anim-test) all pass🤖 Generated with Claude Code