bug(release): rt does not build on Windows — the cut lock calls syscall.Flock, and register-check ignores the allow-list there #1502

Closed
opened 2026-10-02 11:32:39 +02:00 by claude · 1 comment
Member

rt does not compile on Windows. internal/release/lock.go takes the cut-serialization lock with syscall.Flock, and package syscall defines that only on Unix. As a result, no verb is available to anyone working on Windows. That includes the read-only ones a reviewer runs by hand: rt review-eligibility and rt review-submit-check (AGENTS.md section 6.1/6.2), rt register-check, rt fragment-check and rt repin --dry-run.

Measured 2026-10-02 at 29b541b, Go 1.27.1 windows/amd64, Git 2.55.0.windows.3, LF clone:

go build ./cmd/rt
internal\release\lock.go:69:20: undefined: syscall.Flock
internal\release\lock.go:69:47: undefined: syscall.LOCK_EX
internal\release\lock.go:69:63: undefined: syscall.LOCK_NB
internal\release\lock.go:88:23: undefined: syscall.Flock
internal\release\lock.go:88:52: undefined: syscall.LOCK_UN

With a Windows lock backend dropped in, the binary builds. Running it then shows one Windows runtime bug:

rt register-check            (repo root, default surface, lock backend only)
[register-check] FAIL: 71 register-drift hit(s) found across scanned paths.   exit 1

hits by file:  59  docs\retro\phase-7-arc.md
                5  docs\adr\0008-go-port-architecture.md
                3  docs\adr\0009-go-substrate-for-v1.md
                1  docs\adr\0010-concurrency-guard-composition.md
                1  docs\architecture\arc42\11-risks-technical-debt.md
                1  docs\architecture\arc42\12-glossary.md
                1  docs\architecture\contracts\fragment-format.md

Every one of these files is covered by a .register-allowlist entry (docs/adr/, docs/retro/ and the three named files). On Linux the same command reports 0 hits.

Cause. scanOneFile in internal/register/filescan.go receives the OS-native path that filepath.WalkDir produces (docs\retro\...). It matches that path against the forward-slash allow-list entries and self-exclusion keys, which never match, and it also prints the path as the hit's file.

Hooks. On Windows, rt prep with any post_bump_hooks configured stops with prep: post_bump_hook not executable: <hook>. No chmod +x can fix that: os.Stat reports no exec bit on Windows, and CreateProcess does not dispatch on #!. This repository no longer configures hooks, and the release workflow runs on Linux, so only a local Windows rt prep meets this.

Tests. With the lock backend in place, go test -count=1 ./... fails 11 tests natively, in 5 of the 29 packages:

  • the three register tests are the bug above: TestScanTree, TestRegisterFileScanDrift and TestRegisterFileScanExemptAndAllowlist;
  • TestRunPostBumpHooks_theHookDoesNotReceiveTheGitCredential is the hook refusal;
  • the rest come from the test harness, not from rt:
    • a built binary named rt without .exe;
    • POSIX mode bits and chmod 000;
    • a Git Bash locale control;
    • a staged-path lookup keyed with filepath.Join.

TestPushManifestAuthedRejectsSecondPush also fails intermittently on Windows. Its 2 s context deadline can kill git mid-rebase on a busy host. It passed in this run.

Why it matters

  • A reviewer on Windows cannot run the pre-submit gates the review conventions ask for, nor dry-run any verb before a cut.
  • register-check on Windows gives a false red on exactly the paths the allow-list exists to exempt. Its hit lines also carry backslash paths, a form that neither the allow-list format nor the docs use.
  • The cut lock is the only thing in the tree that pins the build to Unix. Everything else is portable Go.

Out of scope

  • Publishing Windows release assets. .goreleaser.yaml builds linux/amd64 only, by the open cross-platform-runners fork in ADR-0009 section 9, which is an operator ratification point.
  • Running post_bump_hooks on Windows. They are POSIX scripts shared with the Linux workflow, so the right move is to refuse them by name.
  • Windows path-handling gaps found only by reading the code, which belong in a separate issue:
    • the version_files guard does not reject a ..\ prefix;
    • resolveUnderRoot treats only a leading / as absolute;
    • an atomic rename fails over a read-only destination.

Acceptance criteria

  • go build ./cmd/rt succeeds natively on Windows. Every non-Windows build keeps the identical flock call. — DONE @ #1505 (b47a824, reviewed as 8e2cf58). go build ./cmd/rt and go vet ./... succeed natively on 96d2646. lock_unix.go is //go:build !windows and carries the same two syscall.Flock calls as the old lock.go (LOCK_EX|LOCK_NB, LOCK_UN).
  • The Windows cut lock has the same contract as flock: exclusive, non-blocking (ErrConcurrentCut on contention), owned by the open file, and dropped by the OS when the holder dies. A second-process test and a killed-holder test cover it, and both go red under a contention-treated-as-success mutant on Windows and Linux. — DONE @ #1505 (b47a824, reviewed as 8e2cf58), with one correction to the mutant wording. Measured natively on Windows and in golang:1.25: a contention-treated-as-success mutant turns TestAcquireLock_anotherProcessHoldsIt and TestNewCutter_concurrentCut red, but not the killed-holder test, which never sees contention. A marker-file mutant (an O_EXCL file instead of the OS lock) turns TestAcquireLock_killedHolderDoesNotStrandTheLock red: stranded after 5 s. Each test bites on the property it pins, as #1505's own mutation table also says.
  • rt register-check on Windows honours .register-allowlist and prints forward-slash paths. On this repository it reports 0 hits, as on Linux. — DONE @ #1505 (90ef268, reviewed as 6aef15b). Native rt register-check on 96d2646: OK: 0 register-drift hits, exit 0, with stdout and stderr byte-identical to the Linux run on the same tree. Forward-slash hits are pinned by TestScanTree and TestScanTreeExplicitOSPath, both PASS natively.
  • On Windows, rt prep refuses configured post_bump_hooks by name, stating the actual cause, before running any of them. Linux behaviour is unchanged. — DONE @ #1505 (25d0c0b, reviewed as 0ac9790). The refusal sits at the top of the per-hook loop, ahead of any exec. TestRunPostBumpHooks_refusalNamesTheCauseOnThisOS PASSES natively with post_bump_hooks are not supported on Windows (no exec bit, no #! dispatch), and in golang:1.25 with the unchanged not executable refusal.
  • go vet ./... && go test -count=1 ./... passes natively on Windows (from Git Bash) and in golang:1.25. On Linux, the output of the touched verbs is byte-identical before and after. — DONE @ #1505 (96d2646, reviewed as ca66a11). Fresh clone of 96d2646: go vet ./... clean and go test -count=1 ./... 29/29, natively from Git Bash and in golang:1.25 as a non-root user. Re-measured on Linux with rt built at 340608c and at 96d2646, both run on the same tree: --help, register-check and fragment-check are byte-identical in stdout, stderr and exit code. prep --dry-run was not re-run here; its byte-identity rests on #1505's review pass (non-executable, failing and succeeding hooks), and its only production change is a runtime.GOOS == "windows" branch.
  • A changelog.d fragment for the change. — DONE @ #1505 (b47a824, reviewed as 8e2cf58). changelog.d/1502-windows-build.fixed.md; rt fragment-check in content mode PASSES.

Anchor

Found while testing a release-notes plan for frankenbit/storey on a Windows workstation. The Flock build failure was already listed as out of scope in #1485.

`rt` does not compile on Windows. `internal/release/lock.go` takes the cut-serialization lock with `syscall.Flock`, and package `syscall` defines that only on Unix. As a result, no verb is available to anyone working on Windows. That includes the read-only ones a reviewer runs by hand: `rt review-eligibility` and `rt review-submit-check` (AGENTS.md section 6.1/6.2), `rt register-check`, `rt fragment-check` and `rt repin --dry-run`. Measured 2026-10-02 at `29b541b`, Go 1.27.1 windows/amd64, Git 2.55.0.windows.3, LF clone: ``` go build ./cmd/rt internal\release\lock.go:69:20: undefined: syscall.Flock internal\release\lock.go:69:47: undefined: syscall.LOCK_EX internal\release\lock.go:69:63: undefined: syscall.LOCK_NB internal\release\lock.go:88:23: undefined: syscall.Flock internal\release\lock.go:88:52: undefined: syscall.LOCK_UN ``` With a Windows lock backend dropped in, the binary builds. Running it then shows one Windows runtime bug: ``` rt register-check (repo root, default surface, lock backend only) [register-check] FAIL: 71 register-drift hit(s) found across scanned paths. exit 1 hits by file: 59 docs\retro\phase-7-arc.md 5 docs\adr\0008-go-port-architecture.md 3 docs\adr\0009-go-substrate-for-v1.md 1 docs\adr\0010-concurrency-guard-composition.md 1 docs\architecture\arc42\11-risks-technical-debt.md 1 docs\architecture\arc42\12-glossary.md 1 docs\architecture\contracts\fragment-format.md ``` Every one of these files is covered by a `.register-allowlist` entry (`docs/adr/`, `docs/retro/` and the three named files). On Linux the same command reports 0 hits. **Cause.** `scanOneFile` in `internal/register/filescan.go` receives the OS-native path that `filepath.WalkDir` produces (`docs\retro\...`). It matches that path against the forward-slash allow-list entries and self-exclusion keys, which never match, and it also prints the path as the hit's file. **Hooks.** On Windows, `rt prep` with any `post_bump_hooks` configured stops with `prep: post_bump_hook not executable: <hook>`. No `chmod +x` can fix that: `os.Stat` reports no exec bit on Windows, and `CreateProcess` does not dispatch on `#!`. This repository no longer configures hooks, and the release workflow runs on Linux, so only a local Windows `rt prep` meets this. **Tests.** With the lock backend in place, `go test -count=1 ./...` fails 11 tests natively, in 5 of the 29 packages: - the three register tests are the bug above: `TestScanTree`, `TestRegisterFileScanDrift` and `TestRegisterFileScanExemptAndAllowlist`; - `TestRunPostBumpHooks_theHookDoesNotReceiveTheGitCredential` is the hook refusal; - the rest come from the test harness, not from `rt`: - a built binary named `rt` without `.exe`; - POSIX mode bits and `chmod 000`; - a Git Bash locale control; - a staged-path lookup keyed with `filepath.Join`. `TestPushManifestAuthedRejectsSecondPush` also fails intermittently on Windows. Its 2 s context deadline can kill git mid-rebase on a busy host. It passed in this run. ## Why it matters - A reviewer on Windows cannot run the pre-submit gates the review conventions ask for, nor dry-run any verb before a cut. - `register-check` on Windows gives a false red on exactly the paths the allow-list exists to exempt. Its hit lines also carry backslash paths, a form that neither the allow-list format nor the docs use. - The cut lock is the only thing in the tree that pins the build to Unix. Everything else is portable Go. ## Out of scope - **Publishing Windows release assets.** `.goreleaser.yaml` builds linux/amd64 only, by the open cross-platform-runners fork in ADR-0009 section 9, which is an operator ratification point. - **Running `post_bump_hooks` on Windows.** They are POSIX scripts shared with the Linux workflow, so the right move is to refuse them by name. - **Windows path-handling gaps found only by reading the code**, which belong in a separate issue: - the `version_files` guard does not reject a `..\` prefix; - `resolveUnderRoot` treats only a leading `/` as absolute; - an atomic rename fails over a read-only destination. ## Acceptance criteria - [x] `go build ./cmd/rt` succeeds natively on Windows. Every non-Windows build keeps the identical `flock` call. — DONE @ #1505 (`b47a824`, reviewed as `8e2cf58`). `go build ./cmd/rt` and `go vet ./...` succeed natively on `96d2646`. `lock_unix.go` is `//go:build !windows` and carries the same two `syscall.Flock` calls as the old `lock.go` (`LOCK_EX|LOCK_NB`, `LOCK_UN`). - [x] The Windows cut lock has the same contract as flock: exclusive, non-blocking (`ErrConcurrentCut` on contention), owned by the open file, and dropped by the OS when the holder dies. A second-process test and a killed-holder test cover it, and both go red under a contention-treated-as-success mutant on Windows and Linux. — DONE @ #1505 (`b47a824`, reviewed as `8e2cf58`), with one correction to the mutant wording. Measured natively on Windows and in `golang:1.25`: a contention-treated-as-success mutant turns `TestAcquireLock_anotherProcessHoldsIt` and `TestNewCutter_concurrentCut` red, but not the killed-holder test, which never sees contention. A marker-file mutant (an `O_EXCL` file instead of the OS lock) turns `TestAcquireLock_killedHolderDoesNotStrandTheLock` red: stranded after 5 s. Each test bites on the property it pins, as #1505's own mutation table also says. - [x] `rt register-check` on Windows honours `.register-allowlist` and prints forward-slash paths. On this repository it reports 0 hits, as on Linux. — DONE @ #1505 (`90ef268`, reviewed as `6aef15b`). Native `rt register-check` on `96d2646`: `OK: 0 register-drift hits`, exit 0, with stdout and stderr byte-identical to the Linux run on the same tree. Forward-slash hits are pinned by `TestScanTree` and `TestScanTreeExplicitOSPath`, both PASS natively. - [x] On Windows, `rt prep` refuses configured `post_bump_hooks` by name, stating the actual cause, before running any of them. Linux behaviour is unchanged. — DONE @ #1505 (`25d0c0b`, reviewed as `0ac9790`). The refusal sits at the top of the per-hook loop, ahead of any exec. `TestRunPostBumpHooks_refusalNamesTheCauseOnThisOS` PASSES natively with `post_bump_hooks are not supported on Windows (no exec bit, no #! dispatch)`, and in `golang:1.25` with the unchanged `not executable` refusal. - [x] `go vet ./... && go test -count=1 ./...` passes natively on Windows (from Git Bash) and in `golang:1.25`. On Linux, the output of the touched verbs is byte-identical before and after. — DONE @ #1505 (`96d2646`, reviewed as `ca66a11`). Fresh clone of `96d2646`: `go vet ./...` clean and `go test -count=1 ./...` 29/29, natively from Git Bash and in `golang:1.25` as a non-root user. Re-measured on Linux with `rt` built at `340608c` and at `96d2646`, both run on the same tree: `--help`, `register-check` and `fragment-check` are byte-identical in stdout, stderr and exit code. `prep --dry-run` was not re-run here; its byte-identity rests on #1505's review pass (non-executable, failing and succeeding hooks), and its only production change is a `runtime.GOOS == "windows"` branch. - [x] A `changelog.d` fragment for the change. — DONE @ #1505 (`b47a824`, reviewed as `8e2cf58`). `changelog.d/1502-windows-build.fixed.md`; `rt fragment-check` in content mode PASSES. ## Anchor Found while testing a release-notes plan for frankenbit/storey on a Windows workstation. The Flock build failure was already listed as out of scope in #1485.
Author
Member

Landed as #1505: b47a824..96d2646, reviewed as 8e2cf58..ca66a11. git range-diff shows all four commits as =. All six criteria are ticked above against live main.

  • Approval: alex merged #1505 after a brief look-through. There is no formal approval row, and the landing identity record (comment 114368) says so.
  • Checks: the combined status on 96d2646 is success, 16 of 16.
  • One wording correction, recorded on the second criterion: the contention mutant reddens the second-process test, and a marker-file mutant reddens the killed-holder test. Measured on both OSes.

The competing #1503 was shut unmerged, and its tracker #1501 is ticked against the same landing.

Shutting this tracker by hand.

Landed as #1505: `b47a824..96d2646`, reviewed as `8e2cf58..ca66a11`. `git range-diff` shows all four commits as `=`. All six criteria are ticked above against live `main`. - **Approval:** alex merged #1505 after a brief look-through. There is no formal approval row, and the landing identity record (comment 114368) says so. - **Checks:** the combined status on `96d2646` is success, 16 of 16. - **One wording correction,** recorded on the second criterion: the contention mutant reddens the second-process test, and a marker-file mutant reddens the killed-holder test. Measured on both OSes. The competing #1503 was shut unmerged, and its tracker #1501 is ticked against the same landing. Shutting this tracker by hand.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
frankenbit/release-toolkit#1502
No description provided.