Repository navigation
build: the repository has no .gitattributes, so a Windows checkout is CRLF and the engine stages every client relation empty #963
Description
Activity
- addedbugSomething isn't workingSomething isn't workingplatformOS / toolchain portabilityOS / toolchain portabilitybuildBuild, packaging and developer setupBuild, packaging and developer setupwindowsMicrosoft Windows supportMicrosoft Windows support
on Sep 18, 2026 Third symptom, and it defeats the engine packaging that merged in #904 an hour ago: a CRLF checkout changes the engine id, so a published engine is refused on every Windows clone.
The id hashes the rule text. Same rules, different line endings, different hash:
LF rules: 0a0c96cbe8cb5dfe... CRLF rules: d269ab85e1583241...Measured by copying the tree, converting
graph/java/**/*.dlto CRLF, and re-runningrun-souffle.sh --language java --print-engine-id. Nothing else changed.run-souffle.shthen reports:! @axiomcode/engine-win32-x64 holds java at <ci id>, these rules are <crlf id> - not using itand falls through to a local compile, which on Windows means souffle, which is the dependency the packaging exists to remove. The refusal is correct given its inputs; the inputs are wrong. Same shape as #895, where the id followed the shell's locale, and the same consequence: the error names the rules when the cause is the environment.
Why the earlier Windows validation missed all three
The five-platform run I did for #904 installed from npm tarballs, and a tarball preserves LF. Every one of these symptoms needs a
git cloneon Windows to appear. So "Windows works" was true for the shipped package and false for anyone working in the repo on Windows, which are two different claims and were being treated as one.Worth stating in the issue because it changes who is affected: not just a Windows developer running the suites, but any Windows machine that would otherwise have consumed a published engine.
Fix
Testing
.gitattributeswith* text=auto eol=lfon a Windows VM now, against a stock clone withcore.autocrlf=true. It also needs explicitbinaryfor*.node,*.tgz,*.sqliteand similar: newline conversion applied to an engine binary corrupts it, and the checksum meant to guard it would be computed over the corruption.Will post the before and after from that machine rather than asserting it works.
- added a commit that references this issue
on Sep 18, 2026 Fixed on main in #964 (
38d4203a):.gitattributespins the tree toeol=lf, with explicitbinaryfor*.node,*.tgz,*.sqliteand similar, since newline conversion applied to an engine binary corrupts it and the checksum guarding it would be computed over the corruption.Leaving this issue open, because one of its three symptoms is not yet pinned down.
Fixed and verified: the
SCHEMA.mdgate (930 CR lines, exactly the byte delta it reports) and the engine id shifting on CRLF rules (0a0c96cbtod269ab85), which silently defeated the engine packaging in #904 for every Windows clone.Not reproduced, and worth someone confirming the environment: the first symptom, every client relation staging empty. On a Windows Server 2022 VM with a stock
autocrlf=trueclone,read_mapdoes not yield a CR-terminated field, because it usesgrepand MSYS2's GNU grep 3.0 strips the CR first:rows read: 26 rows whose last field ends in CR: 0staging.confis clean after sourcing too, since its assignments are quoted and the CR falls outside the value.Another session measured the empty-graph outcome at raw HEAD for Java and Python, so the symptom is real. I am not disputing that; I could not reproduce the stated mechanism under Git Bash, which points at the vector being environment-specific, for instance WSL or any shell whose grep preserves CR. That distinction matters for the inference in that report: if the vector is not the map reader, "same code path, same exposure" is not the argument for TypeScript, JavaScript and C#, though the conclusion may still hold since each language has its own templates.
Whoever picks this up: the useful next measurement is which shell and grep produced the empty graph, then whether the CR survives
read_mapthere. The fix removes the class either way.Fixed by #964 (
38d4203a), which adds the.gitattributesthe repository never had, pinning the tree toeol=lfwith explicitbinaryfor*.node,*.tgz,*.sqliteand similar.All three symptoms resolved:
Empty client relations. Confirmed in practice on Windows: at
38d4203awith a pristine checkout and zero local changes, four of five languages run parse to engine and produce non-empty, semantically correct graphs. Before the fix that run needed 4396 files normalised by hand.The schema gate.
SCHEMA.mdchecked out with 930 CR lines, exactly the byte difference the gate reported as stale, sobin/axiomcode test <lang>aborted before a case ran. Measured on a Windows Server 2022 VM.The engine id. Not in the original report and the most consequential for the engine packaging: the id hashes the rule text, so CRLF rules hash differently. Converting
graph/java/**/*.dlto CRLF and changing nothing else moved the java id from0a0c96cbtod269ab85. A published engine was therefore refused on every Windows clone and fell back to a local compile, which on Windows means souffle, the dependency the packaging exists to remove.One footnote, not a reason to keep this open
The mechanism in the original report is that
IFS=$'\t' readkeeps the trailing CR fromclient-ir.map. That does not reproduce under Git Bash, becauseread_mapusesgrepand MSYS2's GNU grep 3.0 strips the CR first:rows read: 26 rows whose last field ends in CR: 0staging.confis clean after sourcing too, since its assignments are quoted. So the empty-graph outcome was real and is now fixed, but the CR appears to have reached the pipeline by some route other than the map reader, in an environment whose shell or grep preserves it. Pinning that down is not worth an open bug now that the class is gone; if anyone reproduces an empty graph on a current checkout, reopen with the shell and grep version.Note for anyone with an existing clone
.gitattributeschanges what git checks out, so a clone made before38d4203astill has CRLF and still computes the wrong engine id. Fix in place with:git add --renormalize .or re-clone.
Priority: P2 — on a default Windows clone every checked-out text file is CRLF, and the engine's IR-map reader treats the trailing
\ras part of a filename, so every relation is staged empty and the run produces an empty graph with exit 0.Follow-up to #16 (Windows support for the engine pipeline). Measured at
b54d2bd1.Where
The repository ships no
.gitattributes. Git's Windows default iscore.autocrlf=true, so a stockgit cloneon Windows materialises every text file with CRLF endings — 4392 files in this tree.graph/pipeline/run-souffle.sh:209-216stages client facts by reading a tab-separated map:IFS=$'\t'splits on tabs only, so with a CRLF map the last field keeps its\r:csvbecomesall-python-call-sites<CR>. The[ -f "$CLIENT/$csv.csv" ]test then looks for a file whose name contains a carriage return, finds nothing, and takes theelsebranch.Why it is silent
The
elsebranch is deliberate and documented — an absent CSV stages an empty relation so the compiled binary stays reusable across projects (run-souffle.sh:196-207). That is correct behaviour for a project that genuinely has no XML. It is indistinguishable from a project whose every CSV was made unreachable by a stray\r.So the run does not error. It reports
staged 43 relations, solves for several minutes, exits 0, and writes a graph:Measured on both front ends at this HEAD, with a correct non-empty IR on disk (2031 Python call-site rows / 2657 Java
METHOD_INVOCATIONrows staged from). Normalising the tree to LF and re-running the identical command gives 2182 call sites / 2298 edges for the Python project and 3232 call sites / 4488 edges for the Java one.Second symptom: the schema gate can never pass
graph/test/tools/bundle-test.sh:226byte-compares generated output against a committed file:The generator writes LF; the checked-out
SCHEMA.mdis CRLF. The contents are identical:76604 − 75674 = 930, exactly the CRLF count. The gate reports
SCHEMA.md is stale — run: npm run schema-doc, and running it does not help, because the regenerated file is checked back in as CRLF. This abortsbin/axiomcode test <lang>before any case executes.Repro
Fix
Add a
.gitattributesthat pins the files the tooling parses or byte-compares to LF. At minimum the machine-read ones:* text=auto eol=lffor the whole tree is the simpler call if nothing here wants CRLF.Hardening worth considering independently, since a
.gitattributesonly protects fresh checkouts: strip trailing\rinread_map, and make "every client relation staged empty" a diagnosable condition rather than a silent one — a run where no client CSV was found is a broken invocation, not a project with no facts.Acceptance
core.autocrlf=trueproduces a non-empty graph for a project with call sitesbundle-test.sh'sSCHEMA.mdcomparison passes on Windows without regenerating