Skip to content

fix(windows): 46 → 36 failing packages on the experimental CI lane - #25

Merged
Snider merged 5 commits into
mainfrom
lane/win-portability
Aug 8, 2026
Merged

fix(windows): 46 → 36 failing packages on the experimental CI lane#25
Snider merged 5 commits into
mainfrom
lane/win-portability

Conversation

@Snider

@Snider Snider commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Portability fixes for the experimental windows CI lane, each traced to that lane's own log rather than guessed at. 46 → 36 failing packages, with linux + macos (the required checks) green throughout and nothing newly broken.

Measurement

run job failing packages
baseline — main @ 0070ed18 31109255741 46
after commit 1 31247873871 41
after commits 2–3 31248300740 37
after commit 4 31248704361 36

Packages that went green: inference (root), eval/bench, kv/blockcache, lab, model/arch/google/gemma3/gguf, model/bundle, serving/admin, serving/compat, serving/scheduler, train/lora.

Two bugs that were wrong on Windows, not merely red

serving/admin — containment bypass. pathWithinDir's escape arm tested only "../". core.PathRel returns an OS-native relative path, so an escaping result reads "..\evil" on Windows and was waved through as contained. Split into hasParentPrefix, which tests the native separator too.

train/lora — sidecar read from the wrong directory. adapterConfigPath("/models/my-lora/adapter.safetensors") resolved to "./adapter_config.json".

internal/pathx — splitting on either separator

core.PathBase and core.PathDir match only the platform's own separator (core.Env("DS")), so on Windows a /-separated path reads as having no separator at all: PathBase("/models/gemma3-1b") returns the whole string, PathDir("/models/x/adapter.safetensors") returns ".". Go accepts / on every platform and it arrives from config files, CLI flags, HTTP model ids and hub refs.

internal/pathx carries the both-separator Base/Dir/Join. \ is a boundary only where the platform says so — on POSIX it stays an ordinary filename character, which the _Ugly tests pin on both hosts. Four call sites moved across:

  • eval/benchrun.Model is a hub ref (org/model-a) as often as a path; the matrix row was named for the whole ref.
  • train/lora — plus pathx.Join, which keeps whichever separator the input used, so a /-spelled Windows path no longer comes back mixed. The filepath.Clean-free fast path the comment defends is preserved.
  • serving/compatresolverModelNames published the entire ModelPath as the OpenAI model id.
  • model/arch/google/gemma3/ggufgemma3ModelName wrote the whole checkpoint path into general.name.

Clock, encoding, and POSIX-only semantics

  • serving/scheduler — the prefill, total and decode spans used a bare time.Since; Windows' monotonic tick returns exactly 0 for a sub-microsecond span, blanking the durations and, via their positive-span guards, the throughput rates too. measuredSpan floors them.
  • serving/admin (tests) — two reload bodies pasted a path raw into a quoted JSON literal; C:\Users\… decodes as the invalid escape \U.
  • inference (root) — TestGGUF_DiscoverModels_Ugly built its expectation with core.JoinPath (always /) and compared it against a native-separator path; TestDiscover_Good_RelativeBaseDir asked PathRel to cross volumes (CI puts the checkout on D:, TEMP on C:).
  • lab — asserted the POSIX errno text ("in use"); Windows says "Only one usage of each socket address … is normally permitted". Now asserts the syscall stage ("bind:"), which is what the test actually pins.
  • kv/blockcache, model/bundle — five tests inject faults through POSIX-only semantics: chmod 0o500 on a directory (Windows os.Chmod only toggles FILE_ATTRIBUTE_READONLY and still permits creates and unlinks) and a directory's non-zero Stat size (0 on Windows, so ReadFull gets an empty buffer and cannot fail). Skipped there with the reason recorded, so the arms keep their POSIX coverage instead of asserting a false negative.

Ledger — the remaining 36, none fixable here

No replace in go.mod, so all three causes are dependency-side.

30 packages — core/go #21 (open, awaiting review). io.Local is local.New("/"), and "/" is not an absolute root on Windows — it names the current drive. Both failure shapes follow: an absolute C:\Users\… under a D:-rooted medium gives path escapes from parent, and a relative testdata/x.json resolves against the drive root instead of the cwd, giving The system cannot find the path specified. #21's Fs.path fix covers both.

5 packages — dappco.re/go/process v0.16.1. lookPath (exec/exec.go:301) joins dir + file and stats it with no PATHEXT expansion, so git.exe is never found by the bare name git. commandContext (:288) then hand-rolls &exec.Cmd{Path: name} instead of calling exec.CommandContext, so that failure is swallowed rather than surfaced as Cmd.Err — and Go's Windows Cmd.Start resolves the surviving bare name relative to Cmd.Dir, producing exec: "C:\…\acceptance source\git".

1 package — serving/chathistory. DuckDB holds its database file without FILE_SHARE_READ on Windows, so CopyTo's second descriptor onto the live file cannot open. Attempted the DuckDB-native ATTACH + COPY FROM DATABASE and reverted it: the copy does not order tables by foreign key (Violates foreign key constraint because key … does not exist in the referenced table) and it changes the error surface of two further tests. Needs a real fix, not a quick one.

Also parked: train/{,distill/,grpo/}checkpointMetadataPath share the core.PathDir bug fixed elsewhere here, but those packages stay red on #21 regardless, so the change could not be verified green — better done when the lane can prove it.

Receipts

GOWORK=off go test -count=1 ./...      # macOS, full module suite
exit=0 · 176 packages ok
gofmt -l: clean · go vet: clean

CI linux + macos green on every commit in this branch.

The windows CI lane fails 46 of the module suite's packages. This lands the
six whose cause is ours rather than a dependency's, each verified against the
lane's own log (run 31109255741, job 92642423611).

- serving/admin: pathWithinDir's escape arm tested only "../", but PathRel
  hands back an OS-native relative path, so an escaping result reads "..\evil"
  on Windows and was waved through as contained — a containment bypass, not
  just a red test. Split the check into hasParentPrefix, which tests the
  native separator too. Fixes TestPathWithinDir_Containment_Good and
  TestResolveModelNameToPath_SymlinkEscape_Bad.
- serving/admin tests: two reload bodies pasted a filesystem path raw into a
  quoted JSON literal; "C:\Users\..." decodes as the invalid escape "\U".
  Route both through a jsonString helper.
- serving/scheduler: the BeginPrepare span was recorded with a bare
  time.Since, and Windows' monotonic tick returns exactly 0 for a
  sub-microsecond prepare — blanking PrefillDuration and, via its positive-
  span guard, PrefillTokensPerSec as well. Floor it in measuredSpan.
- inference (root): TestGGUF_DiscoverModels_Ugly built its expectation with
  core.JoinPath (always '/') and compared it against a path DiscoverModels
  produced with native separators — PathJoin is the one that matches.
  TestDiscover_Good_RelativeBaseDir asked PathRel to relativise t.TempDir()
  against the cwd, which cannot be expressed when CI puts the checkout on D:
  and TEMP on C:; the base now sits under the cwd.
- kv/blockcache, model/bundle: five tests inject faults through POSIX-only
  filesystem semantics — chmod 0o500 on a directory (Windows os.Chmod only
  toggles FILE_ATTRIBUTE_READONLY and still permits creates and unlinks) and
  a directory's non-zero Stat size (0 on Windows, so ReadFull is handed an
  empty buffer and cannot fail). Skipped there with the reason recorded, so
  the arms keep their POSIX coverage instead of asserting a false negative.

Receipt — macOS, the required lane's platform:
  go test -count=1 ./serving/admin/... ./serving/scheduler/... \
      ./kv/blockcache/... ./model/bundle/... ./lab/... .
  ok  serving/admin 0.870s · serving/scheduler 0.549s · kv/blockcache 1.501s
  ok  model/bundle 1.222s · lab 0.624s · inference 0.316s
  gofmt -l: clean · go vet: clean

lab's TestCmd_RunServe_Bad_ListenAddrInUse asserted the POSIX errno text
("in use"); Windows says "Only one usage of each socket address ... is
normally permitted". It now asserts the syscall stage ("bind:"), which both
report and which is what the test is actually pinning.

The remaining 40 packages are dependency-side and ledgered separately.

Co-Authored-By: Virgil <virgil@lethean.io>
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The changes improve cross-platform test behaviour, strengthen model-path traversal checks, encode filesystem paths correctly in reload tests, and ensure scheduler prefill measurements are at least one nanosecond.

Changes

Cross-platform and runtime behaviour

Layer / File(s) Summary
Portable test behaviour
go/discover_test.go, go/gguf_test.go, go/kv/blockcache/*, go/lab/cmd_test.go, go/model/bundle/bundle_cov_test.go
Tests use native path separators, skip POSIX-only cases on Windows, and check platform-independent bind errors.
Model-path containment validation
go/serving/admin/reload.go, go/serving/admin/admin_test.go
pathWithinDir rejects traversal with either slash convention and rejects absolute results. Reload tests JSON-encode filesystem paths.
Minimum prefill duration
go/serving/scheduler/cb_step.go
Inline and overlapped prefill measurements use measuredSpan, which enforces a minimum duration of one nanosecond.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly states that the pull request fixes Windows CI failures and identifies the reduction in failing packages.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Snider and others added 4 commits August 8, 2026 09:18
…r separator

core.PathBase and core.PathDir match only the platform's own separator
(core.Env("DS")), so on Windows a '/'-separated path reads as having no
separator at all: PathBase("/models/gemma3-1b") returns the whole string,
PathDir("/models/x/adapter.safetensors") returns ".". Go accepts '/' on every
platform and it arrives from config files, CLI flags, HTTP model ids and hub
refs, so four call sites deriving a name or a parent from a caller-supplied
path were wrong there — not merely red under test.

internal/pathx carries the both-separator Base/Dir/Join. '\' is a boundary
only where the platform says so; on POSIX it stays an ordinary filename
character, which the _Ugly tests pin on both hosts.

- eval/bench: run.Model is a hub ref ("org/model-a") as often as a path, and
  those are '/'-separated everywhere — the matrix row was named for the whole
  ref instead of its base.
- train/lora: adapterConfigPath resolved the sidecar of
  "/models/my-lora/adapter.safetensors" to "./adapter_config.json" — the wrong
  directory, not just wrong-looking. Its join also hardcoded '/' onto a
  PathDir result; pathx.Join keeps whichever separator the input used, so a
  '/'-spelled Windows path no longer comes back mixed. The filepath.Clean-free
  fast path the comment defends is preserved.
- serving/compat: resolverModelNames published the entire ModelPath as the
  OpenAI model id.
- model/arch/google/gemma3/gguf: gemma3ModelName wrote the whole checkpoint
  path into general.name.

Receipt — macOS:
  go test -count=1 ./internal/pathx/... ./train/lora/... ./eval/bench/... \
      ./serving/compat/... ./model/arch/google/gemma3/...
  ok  internal/pathx 0.260s · train/lora 0.718s · eval/bench 0.839s
  ok  serving/compat 0.300s · gemma3 0.771s · gemma3/gguf 1.026s
  gofmt -l: clean · go vet: clean · go build ./...: clean

Co-Authored-By: Virgil <virgil@lethean.io>
The windows lane's first run past the prefill fix moved the failure to the
next assertion in the same test: "final durations Total=0s Decode=0s". Same
cause — Windows' monotonic tick cannot resolve the simulated lane's spans, so
time.Since returns exactly 0 — and the same consequence, since
DecodeTokensPerSec's divide is guarded on a positive DecodeDuration.

Route both through measuredSpan, as PrefillDuration already was.

Receipt: 46 → 41 failing packages on the windows lane from the previous
commit (run 31247873871, job 93079323014); serving/scheduler was the one
package whose failure moved rather than cleared.
  go test -count=1 ./serving/scheduler/  →  ok  0.551s

Co-Authored-By: Virgil <virgil@lethean.io>
The pathx switch made TestInspect_Inspect_Good fail on the windows lane — a
test that passed before, so a regression I introduced. It asserted
info.Name != core.PathBase(identityPath), recomputing the expectation with
the same DS-literal helper the production code had just moved off: with
identityPath spelled "/adapters/original/support-tone", Inspect now correctly
answers "support-tone" while core.PathBase on Windows answers the whole path.

Spell the expected name out instead, so the assertion pins the behaviour
rather than a helper that shares the bug.

Receipt: go test -count=1 ./train/lora/  →  ok  0.296s
The other three lora failures this lane started with are already cleared —
run 31248300740 shows Name resolving to "support-tone" on Windows.

Co-Authored-By: Virgil <virgil@lethean.io>
…dent

pathx called core.Env("DS") up to three times per Base/Dir/Join. Env walks
systemInfo's map and falls back to os.Getenv when the key is unset — the
common case for "DS" — and lora's adapterConfigPathPrecomputed, which the
surrounding comment documents as an Inspect hot path, now routes through here.

Cache it behind sync.Once exactly as discover.go's pathSeparator already does
for joinPath/cleanPath, and for the same reason: the override is set once at
process start and never mutates.

Receipt:
  go test -count=1 ./internal/pathx/ ./train/lora/ ./eval/bench/ \
      ./serving/compat/ ./model/arch/google/gemma3/gguf/
  ok  internal/pathx 0.289s · train/lora 0.335s · eval/bench 0.570s
  ok  serving/compat 0.699s · gemma3/gguf 0.947s

Co-Authored-By: Virgil <virgil@lethean.io>
@Snider Snider changed the title fix(windows): six packages off the experimental CI lane's failure list fix(windows): 46 → 36 failing packages on the experimental CI lane Aug 8, 2026
@Snider
Snider merged commit bfe099a into main Aug 8, 2026
3 of 4 checks passed
@Snider
Snider deleted the lane/win-portability branch August 8, 2026 08:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant