fix(release): commit synced versions, add drift guard, Go /v2 module path - #66

Merged
brentrager merged 2 commits into
mainfrom
fix/version-sync
Aug 20, 2026
Merged

fix(release): commit synced versions, add drift guard, Go /v2 module path#66
brentrager merged 2 commits into
mainfrom
fix/version-sync

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

Problem

ci:publish was pnpm build && changeset publish && pnpm run version:sync — the sync ran after publish, mutating manifests in a CI workspace that is never committed. Consequences on main today:

fileversionpackage.json
python/pyproject.toml1.1.52.2.12
rust/file/Cargo.toml1.1.5
go/file/version.go1.1.5
dotnet/**/*.csproj2.2.3

git show go/file/v2.2.12:go/file/version.go1.1.5. And cargo publish --allow-dirty existed only to tolerate that dirt.

Separately, Go requires a /vN module-path suffix for major ≥ 2. The module was github.com/SmooAI/file/go/file while the tags were go/file/v2.2.x, so every tagged version resolved nothinggo get only ever got a pseudo-version off main.

Fix

  • Sync moved into the changesets version lifecycle.version: pnpm run version on the action, where pnpm run version = changeset version && node scripts/sync-versions.mjs. The action commits the working tree after version, so the synced manifests land in the release commit. Removed from ci:publish.
  • scripts/version-targets.mjs is now the single list of version-bearing files. sync-versions.mjs writes it; the new scripts/check-versions.mjs (pnpm version:check) asserts it and exits 1 on drift. Wired into PR checks, release, and check-all. Verified by hand-breaking version.go and go.mod locally — red, then green after sync.
  • A pattern matching nothing is now an error in both scripts. It previously printed "Already up to date", which is the fail-open branch and reads as success.
  • rust/file/Cargo.lock is stamped alongside Cargo.toml (name-targeted, so a same-versioned dependency is never touched), so cargo publish runs --locked with no --allow-dirty.
  • Go module path → github.com/SmooAI/file/go/file/v2, derived from package.json's major, guarded by version:check, and re-asserted in the tag step immediately before an immutable tag is pushed. go build/go vet/go test pass. READMEs updated (root, go, python, rust, dotnet) and the "a proper /v2 module path is planned" note removed.

Judgment calls

  • Patch, not major. The Go module path change breaks Go import paths, but the @smooai/file npm API is untouched and changesets versions all five ports off one number — a major would bump to 3.0.0 and immediately require renaming the module again to /v3. No Go consumer can currently be pinned to a tag (none resolve), so pseudo-version users on main are the only ones affected.
  • sync-versions rewrites go.mod's module suffix rather than only warning about it. A bad rewrite fails loudly: go build/go vet resolve the imports in CI.

Follow-up (not in this PR)

After merge + release, mint the tag and verify resolution against the proxy:
GOFLAGS=-mod=mod go get github.com/SmooAI/file/go/file/v2@v2.2.13

🤖 Generated with Claude Code

https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC

…ule path
`version:sync` ran AFTER `changeset publish`, so it mutated manifests in a CI
workspace nobody committed. Every git tag therefore shipped stale version
constants — `go/file/v2.2.12` contains `Version = "1.1.5"` — and
`cargo publish --allow-dirty` existed only to paper over that dirt.
- Move the sync into the changesets `version` lifecycle (`version: pnpm run
version` on the action), where the working tree IS committed into the release
commit, and drop it from `ci:publish`.
- Split the target list into `scripts/version-targets.mjs` so `sync-versions`
writes it and the new `check-versions` guard asserts it. The guard runs in PR
checks, in release, and in `check-all`; it exits 1 on drift. Verified by
hand-breaking `version.go` and `go.mod` locally.
- A pattern that matches nothing is now an error in both scripts. Previously it
was indistinguishable from "already up to date" — the fail-open branch.
- Stamp `rust/file/Cargo.lock`'s own entry alongside `Cargo.toml`, so
`cargo publish` runs `--locked` with no `--allow-dirty`.
- Go requires a `/vN` module suffix for major >= 2, so `go/file/v2.2.x` tags
resolved nothing. Module path is now `github.com/SmooAI/file/go/file/v2`,
derived from `package.json`, guarded by `version:check`, and re-asserted right
before the release tag is pushed. READMEs updated; the "planned /v2" note is
gone because it now exists.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
@changeset-bot

changeset-botBot commented Aug 20, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7dec49d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
NameType
@smooai/filePatch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

…ld catch
`rust/file/src/lib.rs` asserted `VERSION == "1.1.5"` and
`python/tests/test_basic.py` asserted `__version__ == "1.1.5"` — hardcoded
literals kept in step with the stale constants, so both suites went green while
the repo shipped 2.2.12. A test that asserts the constant it guards locks the
drift in instead of catching it. Both now compare against package.json, the
single source of truth `sync-versions.mjs` copies from.
That surfaced one more version-bearing file the sync never covered:
`python/src/smooai_file/__init__.py` hardcodes `__version__`, so the published
wheel reported 1.1.5 while its own pyproject metadata said 2.2.12. Added to
`version-targets.mjs`, which makes it both synced and guarded.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
@brentrager
brentrager merged commit 028ef26 into mainAug 20, 2026
1 check passed
@brentrager
brentrager deleted the fix/version-sync branch August 20, 2026 18:14
brentrager added a commit that referenced this pull request Aug 20, 2026
All three confirmed live in this repo, not theoretical.
1. `go test ./...` served a CACHED pass against a corrupted fixture. Go's build
cache does not invalidate on a file read from outside the package directory —
exactly the shape of the shared contract fixtures in spec/. Verified by
zeroing every sha256 and setting headBytes to 999: still `ok (cached)`. So
the Go quarter of the five-port lazy contract was proving nothing. `go:test`
now passes `-count=1`; the same corruption fails immediately.
2. release.yml ran `pnpm format` in WRITE mode. Whatever it rewrote was either
swept into the release commit unreviewed, or — in publish mode, where the
changesets action commits nothing — left the tree dirty for
`cargo publish --locked`, which would fail every release now that
`--allow-dirty` is gone (dropped in #66). The step is now `format:check`, and
the formatting the release genuinely needs moved into `pnpm run version`,
before the action commits. Confirmed that changesets' generated CHANGELOG.md
is NOT oxfmt-clean, so without that ordering `format:check` would redden
every future release PR.
3. PyPI, crates.io, NuGet and the Go tag were gated on
`steps.changesets.outputs.published == 'true'` — on npm having published in
THAT run. npm succeeds, a later step fails, the retry finds nothing new for
npm, all four skip: a GREEN run that published nothing, leaving four ports on
the old version indefinitely. Each is now gated on whether its own registry
carries package.json's version, so a retry ships exactly what is missing, and
a final step fails the run if npm published a version the others did not.
`scripts/check-registries.mjs` does the detection, waits out index propagation
(reading "missing" during the lag would skip the publish this run just earned,
then report success), and doubles as a manual "are we stranded?" command.
Verified both directions: all five present on 2.2.14 exits 0; npm present with
the Go tag missing exits 1 naming it.
Checked and NOT stranded today: npm, PyPI, crates.io, both NuGet packages and
the Go tag are all on 2.2.14, so the truncation fix reached every port.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
brentrager added a commit that referenced this pull request Aug 20, 2026
* fix(release): three traps that report success while doing nothing
All three confirmed live in this repo, not theoretical.
1. `go test ./...` served a CACHED pass against a corrupted fixture. Go's build
cache does not invalidate on a file read from outside the package directory —
exactly the shape of the shared contract fixtures in spec/. Verified by
zeroing every sha256 and setting headBytes to 999: still `ok (cached)`. So
the Go quarter of the five-port lazy contract was proving nothing. `go:test`
now passes `-count=1`; the same corruption fails immediately.
2. release.yml ran `pnpm format` in WRITE mode. Whatever it rewrote was either
swept into the release commit unreviewed, or — in publish mode, where the
changesets action commits nothing — left the tree dirty for
`cargo publish --locked`, which would fail every release now that
`--allow-dirty` is gone (dropped in #66). The step is now `format:check`, and
the formatting the release genuinely needs moved into `pnpm run version`,
before the action commits. Confirmed that changesets' generated CHANGELOG.md
is NOT oxfmt-clean, so without that ordering `format:check` would redden
every future release PR.
3. PyPI, crates.io, NuGet and the Go tag were gated on
`steps.changesets.outputs.published == 'true'` — on npm having published in
THAT run. npm succeeds, a later step fails, the retry finds nothing new for
npm, all four skip: a GREEN run that published nothing, leaving four ports on
the old version indefinitely. Each is now gated on whether its own registry
carries package.json's version, so a retry ships exactly what is missing, and
a final step fails the run if npm published a version the others did not.
`scripts/check-registries.mjs` does the detection, waits out index propagation
(reading "missing" during the lag would skip the publish this run just earned,
then report success), and doubles as a manual "are we stranded?" command.
Verified both directions: all five present on 2.2.14 exits 0; npm present with
the Go tag missing exits 1 naming it.
Checked and NOT stranded today: npm, PyPI, crates.io, both NuGet packages and
the Go tag are all on 2.2.14, so the truncation fix reached every port.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
* fix(release): close a fail-open in the registry check, and stop it crying wolf on NuGet
Two corrections found while verifying the previous commit rather than assuming it.
A botched edit had left the npm-probe result being overwritten by a later branch,
so `--expect-npm` returned exit 0 on a version npm did not have. Every downstream
gate is `!has && present.npm`, so a false npm reading would have switched all four
publishes off and let the run go green having shipped nothing — the exact
fail-open this script exists to remove, reintroduced one level up. Now exits 1
with a message naming the disagreement. Verified: exit 1 on an unpublished
version, exit 0 once npm has it.
NuGet is no longer asserted on. Its index takes minutes to tens of minutes to
show a package it has already accepted — 2.2.19 logged "Your package was pushed"
for both packages and the flat-container index still read 2.2.14 twenty minutes
later — so the guard would have reddened every successful release, and a guard
that cries wolf gets deleted. npm, PyPI, crates.io and the Go tag index in
seconds and stay strict. NuGet keeps its own protection: `dotnet nuget push`
exits non-zero on a real failure, and the per-registry gate skips it only when
the version is genuinely already there. Its state is still reported, just not
enforced, and the reason is in the code so it does not read as an oversight.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
* docs(release): correct two workflow comments my own changes made stale
The stranding guard's comment still said "any of the other four" after NuGet was
dropped from the strict set, and the PyPI step still called the wheels it cleans
"pre-sync version" — which stopped being true when sync-versions moved into the
`version` lifecycle. A comment that describes behaviour the code no longer has is
worse than none, because the next reader trusts it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for freeto 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

@brentrager
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(release): commit synced versions, add drift guard, Go /v2 module path - #66

Merged
brentrager merged 2 commits into
mainfrom
fix/version-sync
Aug 20, 2026
Merged

fix(release): commit synced versions, add drift guard, Go /v2 module path#66
brentrager merged 2 commits into
mainfrom
fix/version-sync

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

Problem

ci:publish was pnpm build && changeset publish && pnpm run version:sync — the sync ran after publish, mutating manifests in a CI workspace that is never committed. Consequences on main today:

fileversionpackage.json
python/pyproject.toml1.1.52.2.12
rust/file/Cargo.toml1.1.5
go/file/version.go1.1.5
dotnet/**/*.csproj2.2.3

git show go/file/v2.2.12:go/file/version.go1.1.5. And cargo publish --allow-dirty existed only to tolerate that dirt.

Separately, Go requires a /vN module-path suffix for major ≥ 2. The module was github.com/SmooAI/file/go/file while the tags were go/file/v2.2.x, so every tagged version resolved nothinggo get only ever got a pseudo-version off main.

Fix

  • Sync moved into the changesets version lifecycle.version: pnpm run version on the action, where pnpm run version = changeset version && node scripts/sync-versions.mjs. The action commits the working tree after version, so the synced manifests land in the release commit. Removed from ci:publish.
  • scripts/version-targets.mjs is now the single list of version-bearing files. sync-versions.mjs writes it; the new scripts/check-versions.mjs (pnpm version:check) asserts it and exits 1 on drift. Wired into PR checks, release, and check-all. Verified by hand-breaking version.go and go.mod locally — red, then green after sync.
  • A pattern matching nothing is now an error in both scripts. It previously printed "Already up to date", which is the fail-open branch and reads as success.
  • rust/file/Cargo.lock is stamped alongside Cargo.toml (name-targeted, so a same-versioned dependency is never touched), so cargo publish runs --locked with no --allow-dirty.
  • Go module path → github.com/SmooAI/file/go/file/v2, derived from package.json's major, guarded by version:check, and re-asserted in the tag step immediately before an immutable tag is pushed. go build/go vet/go test pass. READMEs updated (root, go, python, rust, dotnet) and the "a proper /v2 module path is planned" note removed.

Judgment calls

  • Patch, not major. The Go module path change breaks Go import paths, but the @smooai/file npm API is untouched and changesets versions all five ports off one number — a major would bump to 3.0.0 and immediately require renaming the module again to /v3. No Go consumer can currently be pinned to a tag (none resolve), so pseudo-version users on main are the only ones affected.
  • sync-versions rewrites go.mod's module suffix rather than only warning about it. A bad rewrite fails loudly: go build/go vet resolve the imports in CI.

Follow-up (not in this PR)

After merge + release, mint the tag and verify resolution against the proxy:
GOFLAGS=-mod=mod go get github.com/SmooAI/file/go/file/v2@v2.2.13

🤖 Generated with Claude Code

https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC

…ule path
`version:sync` ran AFTER `changeset publish`, so it mutated manifests in a CI
workspace nobody committed. Every git tag therefore shipped stale version
constants — `go/file/v2.2.12` contains `Version = "1.1.5"` — and
`cargo publish --allow-dirty` existed only to paper over that dirt.
- Move the sync into the changesets `version` lifecycle (`version: pnpm run
version` on the action), where the working tree IS committed into the release
commit, and drop it from `ci:publish`.
- Split the target list into `scripts/version-targets.mjs` so `sync-versions`
writes it and the new `check-versions` guard asserts it. The guard runs in PR
checks, in release, and in `check-all`; it exits 1 on drift. Verified by
hand-breaking `version.go` and `go.mod` locally.
- A pattern that matches nothing is now an error in both scripts. Previously it
was indistinguishable from "already up to date" — the fail-open branch.
- Stamp `rust/file/Cargo.lock`'s own entry alongside `Cargo.toml`, so
`cargo publish` runs `--locked` with no `--allow-dirty`.
- Go requires a `/vN` module suffix for major >= 2, so `go/file/v2.2.x` tags
resolved nothing. Module path is now `github.com/SmooAI/file/go/file/v2`,
derived from `package.json`, guarded by `version:check`, and re-asserted right
before the release tag is pushed. READMEs updated; the "planned /v2" note is
gone because it now exists.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
@changeset-bot

changeset-botBot commented Aug 20, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7dec49d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
NameType
@smooai/filePatch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

…ld catch
`rust/file/src/lib.rs` asserted `VERSION == "1.1.5"` and
`python/tests/test_basic.py` asserted `__version__ == "1.1.5"` — hardcoded
literals kept in step with the stale constants, so both suites went green while
the repo shipped 2.2.12. A test that asserts the constant it guards locks the
drift in instead of catching it. Both now compare against package.json, the
single source of truth `sync-versions.mjs` copies from.
That surfaced one more version-bearing file the sync never covered:
`python/src/smooai_file/__init__.py` hardcodes `__version__`, so the published
wheel reported 1.1.5 while its own pyproject metadata said 2.2.12. Added to
`version-targets.mjs`, which makes it both synced and guarded.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
@brentrager
brentrager merged commit 028ef26 into mainAug 20, 2026
1 check passed
@brentrager
brentrager deleted the fix/version-sync branch August 20, 2026 18:14
brentrager added a commit that referenced this pull request Aug 20, 2026
All three confirmed live in this repo, not theoretical.
1. `go test ./...` served a CACHED pass against a corrupted fixture. Go's build
cache does not invalidate on a file read from outside the package directory —
exactly the shape of the shared contract fixtures in spec/. Verified by
zeroing every sha256 and setting headBytes to 999: still `ok (cached)`. So
the Go quarter of the five-port lazy contract was proving nothing. `go:test`
now passes `-count=1`; the same corruption fails immediately.
2. release.yml ran `pnpm format` in WRITE mode. Whatever it rewrote was either
swept into the release commit unreviewed, or — in publish mode, where the
changesets action commits nothing — left the tree dirty for
`cargo publish --locked`, which would fail every release now that
`--allow-dirty` is gone (dropped in #66). The step is now `format:check`, and
the formatting the release genuinely needs moved into `pnpm run version`,
before the action commits. Confirmed that changesets' generated CHANGELOG.md
is NOT oxfmt-clean, so without that ordering `format:check` would redden
every future release PR.
3. PyPI, crates.io, NuGet and the Go tag were gated on
`steps.changesets.outputs.published == 'true'` — on npm having published in
THAT run. npm succeeds, a later step fails, the retry finds nothing new for
npm, all four skip: a GREEN run that published nothing, leaving four ports on
the old version indefinitely. Each is now gated on whether its own registry
carries package.json's version, so a retry ships exactly what is missing, and
a final step fails the run if npm published a version the others did not.
`scripts/check-registries.mjs` does the detection, waits out index propagation
(reading "missing" during the lag would skip the publish this run just earned,
then report success), and doubles as a manual "are we stranded?" command.
Verified both directions: all five present on 2.2.14 exits 0; npm present with
the Go tag missing exits 1 naming it.
Checked and NOT stranded today: npm, PyPI, crates.io, both NuGet packages and
the Go tag are all on 2.2.14, so the truncation fix reached every port.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
brentrager added a commit that referenced this pull request Aug 20, 2026
* fix(release): three traps that report success while doing nothing
All three confirmed live in this repo, not theoretical.
1. `go test ./...` served a CACHED pass against a corrupted fixture. Go's build
cache does not invalidate on a file read from outside the package directory —
exactly the shape of the shared contract fixtures in spec/. Verified by
zeroing every sha256 and setting headBytes to 999: still `ok (cached)`. So
the Go quarter of the five-port lazy contract was proving nothing. `go:test`
now passes `-count=1`; the same corruption fails immediately.
2. release.yml ran `pnpm format` in WRITE mode. Whatever it rewrote was either
swept into the release commit unreviewed, or — in publish mode, where the
changesets action commits nothing — left the tree dirty for
`cargo publish --locked`, which would fail every release now that
`--allow-dirty` is gone (dropped in #66). The step is now `format:check`, and
the formatting the release genuinely needs moved into `pnpm run version`,
before the action commits. Confirmed that changesets' generated CHANGELOG.md
is NOT oxfmt-clean, so without that ordering `format:check` would redden
every future release PR.
3. PyPI, crates.io, NuGet and the Go tag were gated on
`steps.changesets.outputs.published == 'true'` — on npm having published in
THAT run. npm succeeds, a later step fails, the retry finds nothing new for
npm, all four skip: a GREEN run that published nothing, leaving four ports on
the old version indefinitely. Each is now gated on whether its own registry
carries package.json's version, so a retry ships exactly what is missing, and
a final step fails the run if npm published a version the others did not.
`scripts/check-registries.mjs` does the detection, waits out index propagation
(reading "missing" during the lag would skip the publish this run just earned,
then report success), and doubles as a manual "are we stranded?" command.
Verified both directions: all five present on 2.2.14 exits 0; npm present with
the Go tag missing exits 1 naming it.
Checked and NOT stranded today: npm, PyPI, crates.io, both NuGet packages and
the Go tag are all on 2.2.14, so the truncation fix reached every port.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
* fix(release): close a fail-open in the registry check, and stop it crying wolf on NuGet
Two corrections found while verifying the previous commit rather than assuming it.
A botched edit had left the npm-probe result being overwritten by a later branch,
so `--expect-npm` returned exit 0 on a version npm did not have. Every downstream
gate is `!has && present.npm`, so a false npm reading would have switched all four
publishes off and let the run go green having shipped nothing — the exact
fail-open this script exists to remove, reintroduced one level up. Now exits 1
with a message naming the disagreement. Verified: exit 1 on an unpublished
version, exit 0 once npm has it.
NuGet is no longer asserted on. Its index takes minutes to tens of minutes to
show a package it has already accepted — 2.2.19 logged "Your package was pushed"
for both packages and the flat-container index still read 2.2.14 twenty minutes
later — so the guard would have reddened every successful release, and a guard
that cries wolf gets deleted. npm, PyPI, crates.io and the Go tag index in
seconds and stay strict. NuGet keeps its own protection: `dotnet nuget push`
exits non-zero on a real failure, and the per-registry gate skips it only when
the version is genuinely already there. Its state is still reported, just not
enforced, and the reason is in the code so it does not read as an oversight.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
* docs(release): correct two workflow comments my own changes made stale
The stranding guard's comment still said "any of the other four" after NuGet was
dropped from the strict set, and the PyPI step still called the wheels it cleans
"pre-sync version" — which stopped being true when sync-versions moved into the
`version` lifecycle. A comment that describes behaviour the code no longer has is
worse than none, because the next reader trusts it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for freeto 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

@brentrager
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(release): commit synced versions, add drift guard, Go /v2 module path - #66

Merged
brentrager merged 2 commits into
mainfrom
fix/version-sync
Aug 20, 2026
Merged

fix(release): commit synced versions, add drift guard, Go /v2 module path#66
brentrager merged 2 commits into
mainfrom
fix/version-sync

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

Problem

ci:publish was pnpm build && changeset publish && pnpm run version:sync — the sync ran after publish, mutating manifests in a CI workspace that is never committed. Consequences on main today:

fileversionpackage.json
python/pyproject.toml1.1.52.2.12
rust/file/Cargo.toml1.1.5
go/file/version.go1.1.5
dotnet/**/*.csproj2.2.3

git show go/file/v2.2.12:go/file/version.go1.1.5. And cargo publish --allow-dirty existed only to tolerate that dirt.

Separately, Go requires a /vN module-path suffix for major ≥ 2. The module was github.com/SmooAI/file/go/file while the tags were go/file/v2.2.x, so every tagged version resolved nothinggo get only ever got a pseudo-version off main.

Fix

  • Sync moved into the changesets version lifecycle.version: pnpm run version on the action, where pnpm run version = changeset version && node scripts/sync-versions.mjs. The action commits the working tree after version, so the synced manifests land in the release commit. Removed from ci:publish.
  • scripts/version-targets.mjs is now the single list of version-bearing files. sync-versions.mjs writes it; the new scripts/check-versions.mjs (pnpm version:check) asserts it and exits 1 on drift. Wired into PR checks, release, and check-all. Verified by hand-breaking version.go and go.mod locally — red, then green after sync.
  • A pattern matching nothing is now an error in both scripts. It previously printed "Already up to date", which is the fail-open branch and reads as success.
  • rust/file/Cargo.lock is stamped alongside Cargo.toml (name-targeted, so a same-versioned dependency is never touched), so cargo publish runs --locked with no --allow-dirty.
  • Go module path → github.com/SmooAI/file/go/file/v2, derived from package.json's major, guarded by version:check, and re-asserted in the tag step immediately before an immutable tag is pushed. go build/go vet/go test pass. READMEs updated (root, go, python, rust, dotnet) and the "a proper /v2 module path is planned" note removed.

Judgment calls

  • Patch, not major. The Go module path change breaks Go import paths, but the @smooai/file npm API is untouched and changesets versions all five ports off one number — a major would bump to 3.0.0 and immediately require renaming the module again to /v3. No Go consumer can currently be pinned to a tag (none resolve), so pseudo-version users on main are the only ones affected.
  • sync-versions rewrites go.mod's module suffix rather than only warning about it. A bad rewrite fails loudly: go build/go vet resolve the imports in CI.

Follow-up (not in this PR)

After merge + release, mint the tag and verify resolution against the proxy:
GOFLAGS=-mod=mod go get github.com/SmooAI/file/go/file/v2@v2.2.13

🤖 Generated with Claude Code

https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC

…ule path
`version:sync` ran AFTER `changeset publish`, so it mutated manifests in a CI
workspace nobody committed. Every git tag therefore shipped stale version
constants — `go/file/v2.2.12` contains `Version = "1.1.5"` — and
`cargo publish --allow-dirty` existed only to paper over that dirt.
- Move the sync into the changesets `version` lifecycle (`version: pnpm run
version` on the action), where the working tree IS committed into the release
commit, and drop it from `ci:publish`.
- Split the target list into `scripts/version-targets.mjs` so `sync-versions`
writes it and the new `check-versions` guard asserts it. The guard runs in PR
checks, in release, and in `check-all`; it exits 1 on drift. Verified by
hand-breaking `version.go` and `go.mod` locally.
- A pattern that matches nothing is now an error in both scripts. Previously it
was indistinguishable from "already up to date" — the fail-open branch.
- Stamp `rust/file/Cargo.lock`'s own entry alongside `Cargo.toml`, so
`cargo publish` runs `--locked` with no `--allow-dirty`.
- Go requires a `/vN` module suffix for major >= 2, so `go/file/v2.2.x` tags
resolved nothing. Module path is now `github.com/SmooAI/file/go/file/v2`,
derived from `package.json`, guarded by `version:check`, and re-asserted right
before the release tag is pushed. READMEs updated; the "planned /v2" note is
gone because it now exists.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
@changeset-bot

changeset-botBot commented Aug 20, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7dec49d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
NameType
@smooai/filePatch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

…ld catch
`rust/file/src/lib.rs` asserted `VERSION == "1.1.5"` and
`python/tests/test_basic.py` asserted `__version__ == "1.1.5"` — hardcoded
literals kept in step with the stale constants, so both suites went green while
the repo shipped 2.2.12. A test that asserts the constant it guards locks the
drift in instead of catching it. Both now compare against package.json, the
single source of truth `sync-versions.mjs` copies from.
That surfaced one more version-bearing file the sync never covered:
`python/src/smooai_file/__init__.py` hardcodes `__version__`, so the published
wheel reported 1.1.5 while its own pyproject metadata said 2.2.12. Added to
`version-targets.mjs`, which makes it both synced and guarded.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
@brentrager
brentrager merged commit 028ef26 into mainAug 20, 2026
1 check passed
@brentrager
brentrager deleted the fix/version-sync branch August 20, 2026 18:14
brentrager added a commit that referenced this pull request Aug 20, 2026
All three confirmed live in this repo, not theoretical.
1. `go test ./...` served a CACHED pass against a corrupted fixture. Go's build
cache does not invalidate on a file read from outside the package directory —
exactly the shape of the shared contract fixtures in spec/. Verified by
zeroing every sha256 and setting headBytes to 999: still `ok (cached)`. So
the Go quarter of the five-port lazy contract was proving nothing. `go:test`
now passes `-count=1`; the same corruption fails immediately.
2. release.yml ran `pnpm format` in WRITE mode. Whatever it rewrote was either
swept into the release commit unreviewed, or — in publish mode, where the
changesets action commits nothing — left the tree dirty for
`cargo publish --locked`, which would fail every release now that
`--allow-dirty` is gone (dropped in #66). The step is now `format:check`, and
the formatting the release genuinely needs moved into `pnpm run version`,
before the action commits. Confirmed that changesets' generated CHANGELOG.md
is NOT oxfmt-clean, so without that ordering `format:check` would redden
every future release PR.
3. PyPI, crates.io, NuGet and the Go tag were gated on
`steps.changesets.outputs.published == 'true'` — on npm having published in
THAT run. npm succeeds, a later step fails, the retry finds nothing new for
npm, all four skip: a GREEN run that published nothing, leaving four ports on
the old version indefinitely. Each is now gated on whether its own registry
carries package.json's version, so a retry ships exactly what is missing, and
a final step fails the run if npm published a version the others did not.
`scripts/check-registries.mjs` does the detection, waits out index propagation
(reading "missing" during the lag would skip the publish this run just earned,
then report success), and doubles as a manual "are we stranded?" command.
Verified both directions: all five present on 2.2.14 exits 0; npm present with
the Go tag missing exits 1 naming it.
Checked and NOT stranded today: npm, PyPI, crates.io, both NuGet packages and
the Go tag are all on 2.2.14, so the truncation fix reached every port.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
brentrager added a commit that referenced this pull request Aug 20, 2026
* fix(release): three traps that report success while doing nothing
All three confirmed live in this repo, not theoretical.
1. `go test ./...` served a CACHED pass against a corrupted fixture. Go's build
cache does not invalidate on a file read from outside the package directory —
exactly the shape of the shared contract fixtures in spec/. Verified by
zeroing every sha256 and setting headBytes to 999: still `ok (cached)`. So
the Go quarter of the five-port lazy contract was proving nothing. `go:test`
now passes `-count=1`; the same corruption fails immediately.
2. release.yml ran `pnpm format` in WRITE mode. Whatever it rewrote was either
swept into the release commit unreviewed, or — in publish mode, where the
changesets action commits nothing — left the tree dirty for
`cargo publish --locked`, which would fail every release now that
`--allow-dirty` is gone (dropped in #66). The step is now `format:check`, and
the formatting the release genuinely needs moved into `pnpm run version`,
before the action commits. Confirmed that changesets' generated CHANGELOG.md
is NOT oxfmt-clean, so without that ordering `format:check` would redden
every future release PR.
3. PyPI, crates.io, NuGet and the Go tag were gated on
`steps.changesets.outputs.published == 'true'` — on npm having published in
THAT run. npm succeeds, a later step fails, the retry finds nothing new for
npm, all four skip: a GREEN run that published nothing, leaving four ports on
the old version indefinitely. Each is now gated on whether its own registry
carries package.json's version, so a retry ships exactly what is missing, and
a final step fails the run if npm published a version the others did not.
`scripts/check-registries.mjs` does the detection, waits out index propagation
(reading "missing" during the lag would skip the publish this run just earned,
then report success), and doubles as a manual "are we stranded?" command.
Verified both directions: all five present on 2.2.14 exits 0; npm present with
the Go tag missing exits 1 naming it.
Checked and NOT stranded today: npm, PyPI, crates.io, both NuGet packages and
the Go tag are all on 2.2.14, so the truncation fix reached every port.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
* fix(release): close a fail-open in the registry check, and stop it crying wolf on NuGet
Two corrections found while verifying the previous commit rather than assuming it.
A botched edit had left the npm-probe result being overwritten by a later branch,
so `--expect-npm` returned exit 0 on a version npm did not have. Every downstream
gate is `!has && present.npm`, so a false npm reading would have switched all four
publishes off and let the run go green having shipped nothing — the exact
fail-open this script exists to remove, reintroduced one level up. Now exits 1
with a message naming the disagreement. Verified: exit 1 on an unpublished
version, exit 0 once npm has it.
NuGet is no longer asserted on. Its index takes minutes to tens of minutes to
show a package it has already accepted — 2.2.19 logged "Your package was pushed"
for both packages and the flat-container index still read 2.2.14 twenty minutes
later — so the guard would have reddened every successful release, and a guard
that cries wolf gets deleted. npm, PyPI, crates.io and the Go tag index in
seconds and stay strict. NuGet keeps its own protection: `dotnet nuget push`
exits non-zero on a real failure, and the per-registry gate skips it only when
the version is genuinely already there. Its state is still reported, just not
enforced, and the reason is in the code so it does not read as an oversight.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
* docs(release): correct two workflow comments my own changes made stale
The stranding guard's comment still said "any of the other four" after NuGet was
dropped from the strict set, and the PyPI step still called the wheels it cleans
"pre-sync version" — which stopped being true when sync-versions moved into the
`version` lifecycle. A comment that describes behaviour the code no longer has is
worse than none, because the next reader trusts it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for freeto 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

@brentrager
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(release): commit synced versions, add drift guard, Go /v2 module path - #66

Merged
brentrager merged 2 commits into
mainfrom
fix/version-sync
Aug 20, 2026
Merged

fix(release): commit synced versions, add drift guard, Go /v2 module path#66
brentrager merged 2 commits into
mainfrom
fix/version-sync

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

Problem

ci:publish was pnpm build && changeset publish && pnpm run version:sync — the sync ran after publish, mutating manifests in a CI workspace that is never committed. Consequences on main today:

fileversionpackage.json
python/pyproject.toml1.1.52.2.12
rust/file/Cargo.toml1.1.5
go/file/version.go1.1.5
dotnet/**/*.csproj2.2.3

git show go/file/v2.2.12:go/file/version.go1.1.5. And cargo publish --allow-dirty existed only to tolerate that dirt.

Separately, Go requires a /vN module-path suffix for major ≥ 2. The module was github.com/SmooAI/file/go/file while the tags were go/file/v2.2.x, so every tagged version resolved nothinggo get only ever got a pseudo-version off main.

Fix

  • Sync moved into the changesets version lifecycle.version: pnpm run version on the action, where pnpm run version = changeset version && node scripts/sync-versions.mjs. The action commits the working tree after version, so the synced manifests land in the release commit. Removed from ci:publish.
  • scripts/version-targets.mjs is now the single list of version-bearing files. sync-versions.mjs writes it; the new scripts/check-versions.mjs (pnpm version:check) asserts it and exits 1 on drift. Wired into PR checks, release, and check-all. Verified by hand-breaking version.go and go.mod locally — red, then green after sync.
  • A pattern matching nothing is now an error in both scripts. It previously printed "Already up to date", which is the fail-open branch and reads as success.
  • rust/file/Cargo.lock is stamped alongside Cargo.toml (name-targeted, so a same-versioned dependency is never touched), so cargo publish runs --locked with no --allow-dirty.
  • Go module path → github.com/SmooAI/file/go/file/v2, derived from package.json's major, guarded by version:check, and re-asserted in the tag step immediately before an immutable tag is pushed. go build/go vet/go test pass. READMEs updated (root, go, python, rust, dotnet) and the "a proper /v2 module path is planned" note removed.

Judgment calls

  • Patch, not major. The Go module path change breaks Go import paths, but the @smooai/file npm API is untouched and changesets versions all five ports off one number — a major would bump to 3.0.0 and immediately require renaming the module again to /v3. No Go consumer can currently be pinned to a tag (none resolve), so pseudo-version users on main are the only ones affected.
  • sync-versions rewrites go.mod's module suffix rather than only warning about it. A bad rewrite fails loudly: go build/go vet resolve the imports in CI.

Follow-up (not in this PR)

After merge + release, mint the tag and verify resolution against the proxy:
GOFLAGS=-mod=mod go get github.com/SmooAI/file/go/file/v2@v2.2.13

🤖 Generated with Claude Code

https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC

…ule path
`version:sync` ran AFTER `changeset publish`, so it mutated manifests in a CI
workspace nobody committed. Every git tag therefore shipped stale version
constants — `go/file/v2.2.12` contains `Version = "1.1.5"` — and
`cargo publish --allow-dirty` existed only to paper over that dirt.
- Move the sync into the changesets `version` lifecycle (`version: pnpm run
version` on the action), where the working tree IS committed into the release
commit, and drop it from `ci:publish`.
- Split the target list into `scripts/version-targets.mjs` so `sync-versions`
writes it and the new `check-versions` guard asserts it. The guard runs in PR
checks, in release, and in `check-all`; it exits 1 on drift. Verified by
hand-breaking `version.go` and `go.mod` locally.
- A pattern that matches nothing is now an error in both scripts. Previously it
was indistinguishable from "already up to date" — the fail-open branch.
- Stamp `rust/file/Cargo.lock`'s own entry alongside `Cargo.toml`, so
`cargo publish` runs `--locked` with no `--allow-dirty`.
- Go requires a `/vN` module suffix for major >= 2, so `go/file/v2.2.x` tags
resolved nothing. Module path is now `github.com/SmooAI/file/go/file/v2`,
derived from `package.json`, guarded by `version:check`, and re-asserted right
before the release tag is pushed. READMEs updated; the "planned /v2" note is
gone because it now exists.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
@changeset-bot

changeset-botBot commented Aug 20, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7dec49d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
NameType
@smooai/filePatch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

…ld catch
`rust/file/src/lib.rs` asserted `VERSION == "1.1.5"` and
`python/tests/test_basic.py` asserted `__version__ == "1.1.5"` — hardcoded
literals kept in step with the stale constants, so both suites went green while
the repo shipped 2.2.12. A test that asserts the constant it guards locks the
drift in instead of catching it. Both now compare against package.json, the
single source of truth `sync-versions.mjs` copies from.
That surfaced one more version-bearing file the sync never covered:
`python/src/smooai_file/__init__.py` hardcodes `__version__`, so the published
wheel reported 1.1.5 while its own pyproject metadata said 2.2.12. Added to
`version-targets.mjs`, which makes it both synced and guarded.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
@brentrager
brentrager merged commit 028ef26 into mainAug 20, 2026
1 check passed
@brentrager
brentrager deleted the fix/version-sync branch August 20, 2026 18:14
brentrager added a commit that referenced this pull request Aug 20, 2026
All three confirmed live in this repo, not theoretical.
1. `go test ./...` served a CACHED pass against a corrupted fixture. Go's build
cache does not invalidate on a file read from outside the package directory —
exactly the shape of the shared contract fixtures in spec/. Verified by
zeroing every sha256 and setting headBytes to 999: still `ok (cached)`. So
the Go quarter of the five-port lazy contract was proving nothing. `go:test`
now passes `-count=1`; the same corruption fails immediately.
2. release.yml ran `pnpm format` in WRITE mode. Whatever it rewrote was either
swept into the release commit unreviewed, or — in publish mode, where the
changesets action commits nothing — left the tree dirty for
`cargo publish --locked`, which would fail every release now that
`--allow-dirty` is gone (dropped in #66). The step is now `format:check`, and
the formatting the release genuinely needs moved into `pnpm run version`,
before the action commits. Confirmed that changesets' generated CHANGELOG.md
is NOT oxfmt-clean, so without that ordering `format:check` would redden
every future release PR.
3. PyPI, crates.io, NuGet and the Go tag were gated on
`steps.changesets.outputs.published == 'true'` — on npm having published in
THAT run. npm succeeds, a later step fails, the retry finds nothing new for
npm, all four skip: a GREEN run that published nothing, leaving four ports on
the old version indefinitely. Each is now gated on whether its own registry
carries package.json's version, so a retry ships exactly what is missing, and
a final step fails the run if npm published a version the others did not.
`scripts/check-registries.mjs` does the detection, waits out index propagation
(reading "missing" during the lag would skip the publish this run just earned,
then report success), and doubles as a manual "are we stranded?" command.
Verified both directions: all five present on 2.2.14 exits 0; npm present with
the Go tag missing exits 1 naming it.
Checked and NOT stranded today: npm, PyPI, crates.io, both NuGet packages and
the Go tag are all on 2.2.14, so the truncation fix reached every port.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
brentrager added a commit that referenced this pull request Aug 20, 2026
* fix(release): three traps that report success while doing nothing
All three confirmed live in this repo, not theoretical.
1. `go test ./...` served a CACHED pass against a corrupted fixture. Go's build
cache does not invalidate on a file read from outside the package directory —
exactly the shape of the shared contract fixtures in spec/. Verified by
zeroing every sha256 and setting headBytes to 999: still `ok (cached)`. So
the Go quarter of the five-port lazy contract was proving nothing. `go:test`
now passes `-count=1`; the same corruption fails immediately.
2. release.yml ran `pnpm format` in WRITE mode. Whatever it rewrote was either
swept into the release commit unreviewed, or — in publish mode, where the
changesets action commits nothing — left the tree dirty for
`cargo publish --locked`, which would fail every release now that
`--allow-dirty` is gone (dropped in #66). The step is now `format:check`, and
the formatting the release genuinely needs moved into `pnpm run version`,
before the action commits. Confirmed that changesets' generated CHANGELOG.md
is NOT oxfmt-clean, so without that ordering `format:check` would redden
every future release PR.
3. PyPI, crates.io, NuGet and the Go tag were gated on
`steps.changesets.outputs.published == 'true'` — on npm having published in
THAT run. npm succeeds, a later step fails, the retry finds nothing new for
npm, all four skip: a GREEN run that published nothing, leaving four ports on
the old version indefinitely. Each is now gated on whether its own registry
carries package.json's version, so a retry ships exactly what is missing, and
a final step fails the run if npm published a version the others did not.
`scripts/check-registries.mjs` does the detection, waits out index propagation
(reading "missing" during the lag would skip the publish this run just earned,
then report success), and doubles as a manual "are we stranded?" command.
Verified both directions: all five present on 2.2.14 exits 0; npm present with
the Go tag missing exits 1 naming it.
Checked and NOT stranded today: npm, PyPI, crates.io, both NuGet packages and
the Go tag are all on 2.2.14, so the truncation fix reached every port.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
* fix(release): close a fail-open in the registry check, and stop it crying wolf on NuGet
Two corrections found while verifying the previous commit rather than assuming it.
A botched edit had left the npm-probe result being overwritten by a later branch,
so `--expect-npm` returned exit 0 on a version npm did not have. Every downstream
gate is `!has && present.npm`, so a false npm reading would have switched all four
publishes off and let the run go green having shipped nothing — the exact
fail-open this script exists to remove, reintroduced one level up. Now exits 1
with a message naming the disagreement. Verified: exit 1 on an unpublished
version, exit 0 once npm has it.
NuGet is no longer asserted on. Its index takes minutes to tens of minutes to
show a package it has already accepted — 2.2.19 logged "Your package was pushed"
for both packages and the flat-container index still read 2.2.14 twenty minutes
later — so the guard would have reddened every successful release, and a guard
that cries wolf gets deleted. npm, PyPI, crates.io and the Go tag index in
seconds and stay strict. NuGet keeps its own protection: `dotnet nuget push`
exits non-zero on a real failure, and the per-registry gate skips it only when
the version is genuinely already there. Its state is still reported, just not
enforced, and the reason is in the code so it does not read as an oversight.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
* docs(release): correct two workflow comments my own changes made stale
The stranding guard's comment still said "any of the other four" after NuGet was
dropped from the strict set, and the PyPI step still called the wheels it cleans
"pre-sync version" — which stopped being true when sync-versions moved into the
`version` lifecycle. A comment that describes behaviour the code no longer has is
worse than none, because the next reader trusts it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for freeto 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

@brentrager
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(release): commit synced versions, add drift guard, Go /v2 module path - #66

Merged
brentrager merged 2 commits into
mainfrom
fix/version-sync
Aug 20, 2026
Merged

fix(release): commit synced versions, add drift guard, Go /v2 module path#66
brentrager merged 2 commits into
mainfrom
fix/version-sync

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

Problem

ci:publish was pnpm build && changeset publish && pnpm run version:sync — the sync ran after publish, mutating manifests in a CI workspace that is never committed. Consequences on main today:

fileversionpackage.json
python/pyproject.toml1.1.52.2.12
rust/file/Cargo.toml1.1.5
go/file/version.go1.1.5
dotnet/**/*.csproj2.2.3

git show go/file/v2.2.12:go/file/version.go1.1.5. And cargo publish --allow-dirty existed only to tolerate that dirt.

Separately, Go requires a /vN module-path suffix for major ≥ 2. The module was github.com/SmooAI/file/go/file while the tags were go/file/v2.2.x, so every tagged version resolved nothinggo get only ever got a pseudo-version off main.

Fix

  • Sync moved into the changesets version lifecycle.version: pnpm run version on the action, where pnpm run version = changeset version && node scripts/sync-versions.mjs. The action commits the working tree after version, so the synced manifests land in the release commit. Removed from ci:publish.
  • scripts/version-targets.mjs is now the single list of version-bearing files. sync-versions.mjs writes it; the new scripts/check-versions.mjs (pnpm version:check) asserts it and exits 1 on drift. Wired into PR checks, release, and check-all. Verified by hand-breaking version.go and go.mod locally — red, then green after sync.
  • A pattern matching nothing is now an error in both scripts. It previously printed "Already up to date", which is the fail-open branch and reads as success.
  • rust/file/Cargo.lock is stamped alongside Cargo.toml (name-targeted, so a same-versioned dependency is never touched), so cargo publish runs --locked with no --allow-dirty.
  • Go module path → github.com/SmooAI/file/go/file/v2, derived from package.json's major, guarded by version:check, and re-asserted in the tag step immediately before an immutable tag is pushed. go build/go vet/go test pass. READMEs updated (root, go, python, rust, dotnet) and the "a proper /v2 module path is planned" note removed.

Judgment calls

  • Patch, not major. The Go module path change breaks Go import paths, but the @smooai/file npm API is untouched and changesets versions all five ports off one number — a major would bump to 3.0.0 and immediately require renaming the module again to /v3. No Go consumer can currently be pinned to a tag (none resolve), so pseudo-version users on main are the only ones affected.
  • sync-versions rewrites go.mod's module suffix rather than only warning about it. A bad rewrite fails loudly: go build/go vet resolve the imports in CI.

Follow-up (not in this PR)

After merge + release, mint the tag and verify resolution against the proxy:
GOFLAGS=-mod=mod go get github.com/SmooAI/file/go/file/v2@v2.2.13

🤖 Generated with Claude Code

https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC

…ule path
`version:sync` ran AFTER `changeset publish`, so it mutated manifests in a CI
workspace nobody committed. Every git tag therefore shipped stale version
constants — `go/file/v2.2.12` contains `Version = "1.1.5"` — and
`cargo publish --allow-dirty` existed only to paper over that dirt.
- Move the sync into the changesets `version` lifecycle (`version: pnpm run
version` on the action), where the working tree IS committed into the release
commit, and drop it from `ci:publish`.
- Split the target list into `scripts/version-targets.mjs` so `sync-versions`
writes it and the new `check-versions` guard asserts it. The guard runs in PR
checks, in release, and in `check-all`; it exits 1 on drift. Verified by
hand-breaking `version.go` and `go.mod` locally.
- A pattern that matches nothing is now an error in both scripts. Previously it
was indistinguishable from "already up to date" — the fail-open branch.
- Stamp `rust/file/Cargo.lock`'s own entry alongside `Cargo.toml`, so
`cargo publish` runs `--locked` with no `--allow-dirty`.
- Go requires a `/vN` module suffix for major >= 2, so `go/file/v2.2.x` tags
resolved nothing. Module path is now `github.com/SmooAI/file/go/file/v2`,
derived from `package.json`, guarded by `version:check`, and re-asserted right
before the release tag is pushed. READMEs updated; the "planned /v2" note is
gone because it now exists.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
@changeset-bot

changeset-botBot commented Aug 20, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7dec49d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
NameType
@smooai/filePatch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

…ld catch
`rust/file/src/lib.rs` asserted `VERSION == "1.1.5"` and
`python/tests/test_basic.py` asserted `__version__ == "1.1.5"` — hardcoded
literals kept in step with the stale constants, so both suites went green while
the repo shipped 2.2.12. A test that asserts the constant it guards locks the
drift in instead of catching it. Both now compare against package.json, the
single source of truth `sync-versions.mjs` copies from.
That surfaced one more version-bearing file the sync never covered:
`python/src/smooai_file/__init__.py` hardcodes `__version__`, so the published
wheel reported 1.1.5 while its own pyproject metadata said 2.2.12. Added to
`version-targets.mjs`, which makes it both synced and guarded.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
@brentrager
brentrager merged commit 028ef26 into mainAug 20, 2026
1 check passed
@brentrager
brentrager deleted the fix/version-sync branch August 20, 2026 18:14
brentrager added a commit that referenced this pull request Aug 20, 2026
All three confirmed live in this repo, not theoretical.
1. `go test ./...` served a CACHED pass against a corrupted fixture. Go's build
cache does not invalidate on a file read from outside the package directory —
exactly the shape of the shared contract fixtures in spec/. Verified by
zeroing every sha256 and setting headBytes to 999: still `ok (cached)`. So
the Go quarter of the five-port lazy contract was proving nothing. `go:test`
now passes `-count=1`; the same corruption fails immediately.
2. release.yml ran `pnpm format` in WRITE mode. Whatever it rewrote was either
swept into the release commit unreviewed, or — in publish mode, where the
changesets action commits nothing — left the tree dirty for
`cargo publish --locked`, which would fail every release now that
`--allow-dirty` is gone (dropped in #66). The step is now `format:check`, and
the formatting the release genuinely needs moved into `pnpm run version`,
before the action commits. Confirmed that changesets' generated CHANGELOG.md
is NOT oxfmt-clean, so without that ordering `format:check` would redden
every future release PR.
3. PyPI, crates.io, NuGet and the Go tag were gated on
`steps.changesets.outputs.published == 'true'` — on npm having published in
THAT run. npm succeeds, a later step fails, the retry finds nothing new for
npm, all four skip: a GREEN run that published nothing, leaving four ports on
the old version indefinitely. Each is now gated on whether its own registry
carries package.json's version, so a retry ships exactly what is missing, and
a final step fails the run if npm published a version the others did not.
`scripts/check-registries.mjs` does the detection, waits out index propagation
(reading "missing" during the lag would skip the publish this run just earned,
then report success), and doubles as a manual "are we stranded?" command.
Verified both directions: all five present on 2.2.14 exits 0; npm present with
the Go tag missing exits 1 naming it.
Checked and NOT stranded today: npm, PyPI, crates.io, both NuGet packages and
the Go tag are all on 2.2.14, so the truncation fix reached every port.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
brentrager added a commit that referenced this pull request Aug 20, 2026
* fix(release): three traps that report success while doing nothing
All three confirmed live in this repo, not theoretical.
1. `go test ./...` served a CACHED pass against a corrupted fixture. Go's build
cache does not invalidate on a file read from outside the package directory —
exactly the shape of the shared contract fixtures in spec/. Verified by
zeroing every sha256 and setting headBytes to 999: still `ok (cached)`. So
the Go quarter of the five-port lazy contract was proving nothing. `go:test`
now passes `-count=1`; the same corruption fails immediately.
2. release.yml ran `pnpm format` in WRITE mode. Whatever it rewrote was either
swept into the release commit unreviewed, or — in publish mode, where the
changesets action commits nothing — left the tree dirty for
`cargo publish --locked`, which would fail every release now that
`--allow-dirty` is gone (dropped in #66). The step is now `format:check`, and
the formatting the release genuinely needs moved into `pnpm run version`,
before the action commits. Confirmed that changesets' generated CHANGELOG.md
is NOT oxfmt-clean, so without that ordering `format:check` would redden
every future release PR.
3. PyPI, crates.io, NuGet and the Go tag were gated on
`steps.changesets.outputs.published == 'true'` — on npm having published in
THAT run. npm succeeds, a later step fails, the retry finds nothing new for
npm, all four skip: a GREEN run that published nothing, leaving four ports on
the old version indefinitely. Each is now gated on whether its own registry
carries package.json's version, so a retry ships exactly what is missing, and
a final step fails the run if npm published a version the others did not.
`scripts/check-registries.mjs` does the detection, waits out index propagation
(reading "missing" during the lag would skip the publish this run just earned,
then report success), and doubles as a manual "are we stranded?" command.
Verified both directions: all five present on 2.2.14 exits 0; npm present with
the Go tag missing exits 1 naming it.
Checked and NOT stranded today: npm, PyPI, crates.io, both NuGet packages and
the Go tag are all on 2.2.14, so the truncation fix reached every port.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
* fix(release): close a fail-open in the registry check, and stop it crying wolf on NuGet
Two corrections found while verifying the previous commit rather than assuming it.
A botched edit had left the npm-probe result being overwritten by a later branch,
so `--expect-npm` returned exit 0 on a version npm did not have. Every downstream
gate is `!has && present.npm`, so a false npm reading would have switched all four
publishes off and let the run go green having shipped nothing — the exact
fail-open this script exists to remove, reintroduced one level up. Now exits 1
with a message naming the disagreement. Verified: exit 1 on an unpublished
version, exit 0 once npm has it.
NuGet is no longer asserted on. Its index takes minutes to tens of minutes to
show a package it has already accepted — 2.2.19 logged "Your package was pushed"
for both packages and the flat-container index still read 2.2.14 twenty minutes
later — so the guard would have reddened every successful release, and a guard
that cries wolf gets deleted. npm, PyPI, crates.io and the Go tag index in
seconds and stay strict. NuGet keeps its own protection: `dotnet nuget push`
exits non-zero on a real failure, and the per-registry gate skips it only when
the version is genuinely already there. Its state is still reported, just not
enforced, and the reason is in the code so it does not read as an oversight.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
* docs(release): correct two workflow comments my own changes made stale
The stranding guard's comment still said "any of the other four" after NuGet was
dropped from the strict set, and the PyPI step still called the wheels it cleans
"pre-sync version" — which stopped being true when sync-versions moved into the
`version` lifecycle. A comment that describes behaviour the code no longer has is
worse than none, because the next reader trusts it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for freeto 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

@brentrager
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(release): commit synced versions, add drift guard, Go /v2 module path - #66

Merged
brentrager merged 2 commits into
mainfrom
fix/version-sync
Aug 20, 2026
Merged

fix(release): commit synced versions, add drift guard, Go /v2 module path#66
brentrager merged 2 commits into
mainfrom
fix/version-sync

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

Problem

ci:publish was pnpm build && changeset publish && pnpm run version:sync — the sync ran after publish, mutating manifests in a CI workspace that is never committed. Consequences on main today:

fileversionpackage.json
python/pyproject.toml1.1.52.2.12
rust/file/Cargo.toml1.1.5
go/file/version.go1.1.5
dotnet/**/*.csproj2.2.3

git show go/file/v2.2.12:go/file/version.go1.1.5. And cargo publish --allow-dirty existed only to tolerate that dirt.

Separately, Go requires a /vN module-path suffix for major ≥ 2. The module was github.com/SmooAI/file/go/file while the tags were go/file/v2.2.x, so every tagged version resolved nothinggo get only ever got a pseudo-version off main.

Fix

  • Sync moved into the changesets version lifecycle.version: pnpm run version on the action, where pnpm run version = changeset version && node scripts/sync-versions.mjs. The action commits the working tree after version, so the synced manifests land in the release commit. Removed from ci:publish.
  • scripts/version-targets.mjs is now the single list of version-bearing files. sync-versions.mjs writes it; the new scripts/check-versions.mjs (pnpm version:check) asserts it and exits 1 on drift. Wired into PR checks, release, and check-all. Verified by hand-breaking version.go and go.mod locally — red, then green after sync.
  • A pattern matching nothing is now an error in both scripts. It previously printed "Already up to date", which is the fail-open branch and reads as success.
  • rust/file/Cargo.lock is stamped alongside Cargo.toml (name-targeted, so a same-versioned dependency is never touched), so cargo publish runs --locked with no --allow-dirty.
  • Go module path → github.com/SmooAI/file/go/file/v2, derived from package.json's major, guarded by version:check, and re-asserted in the tag step immediately before an immutable tag is pushed. go build/go vet/go test pass. READMEs updated (root, go, python, rust, dotnet) and the "a proper /v2 module path is planned" note removed.

Judgment calls

  • Patch, not major. The Go module path change breaks Go import paths, but the @smooai/file npm API is untouched and changesets versions all five ports off one number — a major would bump to 3.0.0 and immediately require renaming the module again to /v3. No Go consumer can currently be pinned to a tag (none resolve), so pseudo-version users on main are the only ones affected.
  • sync-versions rewrites go.mod's module suffix rather than only warning about it. A bad rewrite fails loudly: go build/go vet resolve the imports in CI.

Follow-up (not in this PR)

After merge + release, mint the tag and verify resolution against the proxy:
GOFLAGS=-mod=mod go get github.com/SmooAI/file/go/file/v2@v2.2.13

🤖 Generated with Claude Code

https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC

…ule path
`version:sync` ran AFTER `changeset publish`, so it mutated manifests in a CI
workspace nobody committed. Every git tag therefore shipped stale version
constants — `go/file/v2.2.12` contains `Version = "1.1.5"` — and
`cargo publish --allow-dirty` existed only to paper over that dirt.
- Move the sync into the changesets `version` lifecycle (`version: pnpm run
version` on the action), where the working tree IS committed into the release
commit, and drop it from `ci:publish`.
- Split the target list into `scripts/version-targets.mjs` so `sync-versions`
writes it and the new `check-versions` guard asserts it. The guard runs in PR
checks, in release, and in `check-all`; it exits 1 on drift. Verified by
hand-breaking `version.go` and `go.mod` locally.
- A pattern that matches nothing is now an error in both scripts. Previously it
was indistinguishable from "already up to date" — the fail-open branch.
- Stamp `rust/file/Cargo.lock`'s own entry alongside `Cargo.toml`, so
`cargo publish` runs `--locked` with no `--allow-dirty`.
- Go requires a `/vN` module suffix for major >= 2, so `go/file/v2.2.x` tags
resolved nothing. Module path is now `github.com/SmooAI/file/go/file/v2`,
derived from `package.json`, guarded by `version:check`, and re-asserted right
before the release tag is pushed. READMEs updated; the "planned /v2" note is
gone because it now exists.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
@changeset-bot

changeset-botBot commented Aug 20, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7dec49d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
NameType
@smooai/filePatch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

…ld catch
`rust/file/src/lib.rs` asserted `VERSION == "1.1.5"` and
`python/tests/test_basic.py` asserted `__version__ == "1.1.5"` — hardcoded
literals kept in step with the stale constants, so both suites went green while
the repo shipped 2.2.12. A test that asserts the constant it guards locks the
drift in instead of catching it. Both now compare against package.json, the
single source of truth `sync-versions.mjs` copies from.
That surfaced one more version-bearing file the sync never covered:
`python/src/smooai_file/__init__.py` hardcodes `__version__`, so the published
wheel reported 1.1.5 while its own pyproject metadata said 2.2.12. Added to
`version-targets.mjs`, which makes it both synced and guarded.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
@brentrager
brentrager merged commit 028ef26 into mainAug 20, 2026
1 check passed
@brentrager
brentrager deleted the fix/version-sync branch August 20, 2026 18:14
brentrager added a commit that referenced this pull request Aug 20, 2026
All three confirmed live in this repo, not theoretical.
1. `go test ./...` served a CACHED pass against a corrupted fixture. Go's build
cache does not invalidate on a file read from outside the package directory —
exactly the shape of the shared contract fixtures in spec/. Verified by
zeroing every sha256 and setting headBytes to 999: still `ok (cached)`. So
the Go quarter of the five-port lazy contract was proving nothing. `go:test`
now passes `-count=1`; the same corruption fails immediately.
2. release.yml ran `pnpm format` in WRITE mode. Whatever it rewrote was either
swept into the release commit unreviewed, or — in publish mode, where the
changesets action commits nothing — left the tree dirty for
`cargo publish --locked`, which would fail every release now that
`--allow-dirty` is gone (dropped in #66). The step is now `format:check`, and
the formatting the release genuinely needs moved into `pnpm run version`,
before the action commits. Confirmed that changesets' generated CHANGELOG.md
is NOT oxfmt-clean, so without that ordering `format:check` would redden
every future release PR.
3. PyPI, crates.io, NuGet and the Go tag were gated on
`steps.changesets.outputs.published == 'true'` — on npm having published in
THAT run. npm succeeds, a later step fails, the retry finds nothing new for
npm, all four skip: a GREEN run that published nothing, leaving four ports on
the old version indefinitely. Each is now gated on whether its own registry
carries package.json's version, so a retry ships exactly what is missing, and
a final step fails the run if npm published a version the others did not.
`scripts/check-registries.mjs` does the detection, waits out index propagation
(reading "missing" during the lag would skip the publish this run just earned,
then report success), and doubles as a manual "are we stranded?" command.
Verified both directions: all five present on 2.2.14 exits 0; npm present with
the Go tag missing exits 1 naming it.
Checked and NOT stranded today: npm, PyPI, crates.io, both NuGet packages and
the Go tag are all on 2.2.14, so the truncation fix reached every port.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
brentrager added a commit that referenced this pull request Aug 20, 2026
* fix(release): three traps that report success while doing nothing
All three confirmed live in this repo, not theoretical.
1. `go test ./...` served a CACHED pass against a corrupted fixture. Go's build
cache does not invalidate on a file read from outside the package directory —
exactly the shape of the shared contract fixtures in spec/. Verified by
zeroing every sha256 and setting headBytes to 999: still `ok (cached)`. So
the Go quarter of the five-port lazy contract was proving nothing. `go:test`
now passes `-count=1`; the same corruption fails immediately.
2. release.yml ran `pnpm format` in WRITE mode. Whatever it rewrote was either
swept into the release commit unreviewed, or — in publish mode, where the
changesets action commits nothing — left the tree dirty for
`cargo publish --locked`, which would fail every release now that
`--allow-dirty` is gone (dropped in #66). The step is now `format:check`, and
the formatting the release genuinely needs moved into `pnpm run version`,
before the action commits. Confirmed that changesets' generated CHANGELOG.md
is NOT oxfmt-clean, so without that ordering `format:check` would redden
every future release PR.
3. PyPI, crates.io, NuGet and the Go tag were gated on
`steps.changesets.outputs.published == 'true'` — on npm having published in
THAT run. npm succeeds, a later step fails, the retry finds nothing new for
npm, all four skip: a GREEN run that published nothing, leaving four ports on
the old version indefinitely. Each is now gated on whether its own registry
carries package.json's version, so a retry ships exactly what is missing, and
a final step fails the run if npm published a version the others did not.
`scripts/check-registries.mjs` does the detection, waits out index propagation
(reading "missing" during the lag would skip the publish this run just earned,
then report success), and doubles as a manual "are we stranded?" command.
Verified both directions: all five present on 2.2.14 exits 0; npm present with
the Go tag missing exits 1 naming it.
Checked and NOT stranded today: npm, PyPI, crates.io, both NuGet packages and
the Go tag are all on 2.2.14, so the truncation fix reached every port.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
* fix(release): close a fail-open in the registry check, and stop it crying wolf on NuGet
Two corrections found while verifying the previous commit rather than assuming it.
A botched edit had left the npm-probe result being overwritten by a later branch,
so `--expect-npm` returned exit 0 on a version npm did not have. Every downstream
gate is `!has && present.npm`, so a false npm reading would have switched all four
publishes off and let the run go green having shipped nothing — the exact
fail-open this script exists to remove, reintroduced one level up. Now exits 1
with a message naming the disagreement. Verified: exit 1 on an unpublished
version, exit 0 once npm has it.
NuGet is no longer asserted on. Its index takes minutes to tens of minutes to
show a package it has already accepted — 2.2.19 logged "Your package was pushed"
for both packages and the flat-container index still read 2.2.14 twenty minutes
later — so the guard would have reddened every successful release, and a guard
that cries wolf gets deleted. npm, PyPI, crates.io and the Go tag index in
seconds and stay strict. NuGet keeps its own protection: `dotnet nuget push`
exits non-zero on a real failure, and the per-registry gate skips it only when
the version is genuinely already there. Its state is still reported, just not
enforced, and the reason is in the code so it does not read as an oversight.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
* docs(release): correct two workflow comments my own changes made stale
The stranding guard's comment still said "any of the other four" after NuGet was
dropped from the strict set, and the PyPI step still called the wheels it cleans
"pre-sync version" — which stopped being true when sync-versions moved into the
`version` lifecycle. A comment that describes behaviour the code no longer has is
worse than none, because the next reader trusts it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for freeto 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

@brentrager
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(release): commit synced versions, add drift guard, Go /v2 module path - #66

Merged
brentrager merged 2 commits into
mainfrom
fix/version-sync
Aug 20, 2026
Merged

fix(release): commit synced versions, add drift guard, Go /v2 module path#66
brentrager merged 2 commits into
mainfrom
fix/version-sync

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

Problem

ci:publish was pnpm build && changeset publish && pnpm run version:sync — the sync ran after publish, mutating manifests in a CI workspace that is never committed. Consequences on main today:

fileversionpackage.json
python/pyproject.toml1.1.52.2.12
rust/file/Cargo.toml1.1.5
go/file/version.go1.1.5
dotnet/**/*.csproj2.2.3

git show go/file/v2.2.12:go/file/version.go1.1.5. And cargo publish --allow-dirty existed only to tolerate that dirt.

Separately, Go requires a /vN module-path suffix for major ≥ 2. The module was github.com/SmooAI/file/go/file while the tags were go/file/v2.2.x, so every tagged version resolved nothinggo get only ever got a pseudo-version off main.

Fix

  • Sync moved into the changesets version lifecycle.version: pnpm run version on the action, where pnpm run version = changeset version && node scripts/sync-versions.mjs. The action commits the working tree after version, so the synced manifests land in the release commit. Removed from ci:publish.
  • scripts/version-targets.mjs is now the single list of version-bearing files. sync-versions.mjs writes it; the new scripts/check-versions.mjs (pnpm version:check) asserts it and exits 1 on drift. Wired into PR checks, release, and check-all. Verified by hand-breaking version.go and go.mod locally — red, then green after sync.
  • A pattern matching nothing is now an error in both scripts. It previously printed "Already up to date", which is the fail-open branch and reads as success.
  • rust/file/Cargo.lock is stamped alongside Cargo.toml (name-targeted, so a same-versioned dependency is never touched), so cargo publish runs --locked with no --allow-dirty.
  • Go module path → github.com/SmooAI/file/go/file/v2, derived from package.json's major, guarded by version:check, and re-asserted in the tag step immediately before an immutable tag is pushed. go build/go vet/go test pass. READMEs updated (root, go, python, rust, dotnet) and the "a proper /v2 module path is planned" note removed.

Judgment calls

  • Patch, not major. The Go module path change breaks Go import paths, but the @smooai/file npm API is untouched and changesets versions all five ports off one number — a major would bump to 3.0.0 and immediately require renaming the module again to /v3. No Go consumer can currently be pinned to a tag (none resolve), so pseudo-version users on main are the only ones affected.
  • sync-versions rewrites go.mod's module suffix rather than only warning about it. A bad rewrite fails loudly: go build/go vet resolve the imports in CI.

Follow-up (not in this PR)

After merge + release, mint the tag and verify resolution against the proxy:
GOFLAGS=-mod=mod go get github.com/SmooAI/file/go/file/v2@v2.2.13

🤖 Generated with Claude Code

https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC

…ule path
`version:sync` ran AFTER `changeset publish`, so it mutated manifests in a CI
workspace nobody committed. Every git tag therefore shipped stale version
constants — `go/file/v2.2.12` contains `Version = "1.1.5"` — and
`cargo publish --allow-dirty` existed only to paper over that dirt.
- Move the sync into the changesets `version` lifecycle (`version: pnpm run
version` on the action), where the working tree IS committed into the release
commit, and drop it from `ci:publish`.
- Split the target list into `scripts/version-targets.mjs` so `sync-versions`
writes it and the new `check-versions` guard asserts it. The guard runs in PR
checks, in release, and in `check-all`; it exits 1 on drift. Verified by
hand-breaking `version.go` and `go.mod` locally.
- A pattern that matches nothing is now an error in both scripts. Previously it
was indistinguishable from "already up to date" — the fail-open branch.
- Stamp `rust/file/Cargo.lock`'s own entry alongside `Cargo.toml`, so
`cargo publish` runs `--locked` with no `--allow-dirty`.
- Go requires a `/vN` module suffix for major >= 2, so `go/file/v2.2.x` tags
resolved nothing. Module path is now `github.com/SmooAI/file/go/file/v2`,
derived from `package.json`, guarded by `version:check`, and re-asserted right
before the release tag is pushed. READMEs updated; the "planned /v2" note is
gone because it now exists.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
@changeset-bot

changeset-botBot commented Aug 20, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7dec49d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
NameType
@smooai/filePatch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

…ld catch
`rust/file/src/lib.rs` asserted `VERSION == "1.1.5"` and
`python/tests/test_basic.py` asserted `__version__ == "1.1.5"` — hardcoded
literals kept in step with the stale constants, so both suites went green while
the repo shipped 2.2.12. A test that asserts the constant it guards locks the
drift in instead of catching it. Both now compare against package.json, the
single source of truth `sync-versions.mjs` copies from.
That surfaced one more version-bearing file the sync never covered:
`python/src/smooai_file/__init__.py` hardcodes `__version__`, so the published
wheel reported 1.1.5 while its own pyproject metadata said 2.2.12. Added to
`version-targets.mjs`, which makes it both synced and guarded.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
@brentrager
brentrager merged commit 028ef26 into mainAug 20, 2026
1 check passed
@brentrager
brentrager deleted the fix/version-sync branch August 20, 2026 18:14
brentrager added a commit that referenced this pull request Aug 20, 2026
All three confirmed live in this repo, not theoretical.
1. `go test ./...` served a CACHED pass against a corrupted fixture. Go's build
cache does not invalidate on a file read from outside the package directory —
exactly the shape of the shared contract fixtures in spec/. Verified by
zeroing every sha256 and setting headBytes to 999: still `ok (cached)`. So
the Go quarter of the five-port lazy contract was proving nothing. `go:test`
now passes `-count=1`; the same corruption fails immediately.
2. release.yml ran `pnpm format` in WRITE mode. Whatever it rewrote was either
swept into the release commit unreviewed, or — in publish mode, where the
changesets action commits nothing — left the tree dirty for
`cargo publish --locked`, which would fail every release now that
`--allow-dirty` is gone (dropped in #66). The step is now `format:check`, and
the formatting the release genuinely needs moved into `pnpm run version`,
before the action commits. Confirmed that changesets' generated CHANGELOG.md
is NOT oxfmt-clean, so without that ordering `format:check` would redden
every future release PR.
3. PyPI, crates.io, NuGet and the Go tag were gated on
`steps.changesets.outputs.published == 'true'` — on npm having published in
THAT run. npm succeeds, a later step fails, the retry finds nothing new for
npm, all four skip: a GREEN run that published nothing, leaving four ports on
the old version indefinitely. Each is now gated on whether its own registry
carries package.json's version, so a retry ships exactly what is missing, and
a final step fails the run if npm published a version the others did not.
`scripts/check-registries.mjs` does the detection, waits out index propagation
(reading "missing" during the lag would skip the publish this run just earned,
then report success), and doubles as a manual "are we stranded?" command.
Verified both directions: all five present on 2.2.14 exits 0; npm present with
the Go tag missing exits 1 naming it.
Checked and NOT stranded today: npm, PyPI, crates.io, both NuGet packages and
the Go tag are all on 2.2.14, so the truncation fix reached every port.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
brentrager added a commit that referenced this pull request Aug 20, 2026
* fix(release): three traps that report success while doing nothing
All three confirmed live in this repo, not theoretical.
1. `go test ./...` served a CACHED pass against a corrupted fixture. Go's build
cache does not invalidate on a file read from outside the package directory —
exactly the shape of the shared contract fixtures in spec/. Verified by
zeroing every sha256 and setting headBytes to 999: still `ok (cached)`. So
the Go quarter of the five-port lazy contract was proving nothing. `go:test`
now passes `-count=1`; the same corruption fails immediately.
2. release.yml ran `pnpm format` in WRITE mode. Whatever it rewrote was either
swept into the release commit unreviewed, or — in publish mode, where the
changesets action commits nothing — left the tree dirty for
`cargo publish --locked`, which would fail every release now that
`--allow-dirty` is gone (dropped in #66). The step is now `format:check`, and
the formatting the release genuinely needs moved into `pnpm run version`,
before the action commits. Confirmed that changesets' generated CHANGELOG.md
is NOT oxfmt-clean, so without that ordering `format:check` would redden
every future release PR.
3. PyPI, crates.io, NuGet and the Go tag were gated on
`steps.changesets.outputs.published == 'true'` — on npm having published in
THAT run. npm succeeds, a later step fails, the retry finds nothing new for
npm, all four skip: a GREEN run that published nothing, leaving four ports on
the old version indefinitely. Each is now gated on whether its own registry
carries package.json's version, so a retry ships exactly what is missing, and
a final step fails the run if npm published a version the others did not.
`scripts/check-registries.mjs` does the detection, waits out index propagation
(reading "missing" during the lag would skip the publish this run just earned,
then report success), and doubles as a manual "are we stranded?" command.
Verified both directions: all five present on 2.2.14 exits 0; npm present with
the Go tag missing exits 1 naming it.
Checked and NOT stranded today: npm, PyPI, crates.io, both NuGet packages and
the Go tag are all on 2.2.14, so the truncation fix reached every port.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
* fix(release): close a fail-open in the registry check, and stop it crying wolf on NuGet
Two corrections found while verifying the previous commit rather than assuming it.
A botched edit had left the npm-probe result being overwritten by a later branch,
so `--expect-npm` returned exit 0 on a version npm did not have. Every downstream
gate is `!has && present.npm`, so a false npm reading would have switched all four
publishes off and let the run go green having shipped nothing — the exact
fail-open this script exists to remove, reintroduced one level up. Now exits 1
with a message naming the disagreement. Verified: exit 1 on an unpublished
version, exit 0 once npm has it.
NuGet is no longer asserted on. Its index takes minutes to tens of minutes to
show a package it has already accepted — 2.2.19 logged "Your package was pushed"
for both packages and the flat-container index still read 2.2.14 twenty minutes
later — so the guard would have reddened every successful release, and a guard
that cries wolf gets deleted. npm, PyPI, crates.io and the Go tag index in
seconds and stay strict. NuGet keeps its own protection: `dotnet nuget push`
exits non-zero on a real failure, and the per-registry gate skips it only when
the version is genuinely already there. Its state is still reported, just not
enforced, and the reason is in the code so it does not read as an oversight.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
* docs(release): correct two workflow comments my own changes made stale
The stranding guard's comment still said "any of the other four" after NuGet was
dropped from the strict set, and the PyPI step still called the wheels it cleans
"pre-sync version" — which stopped being true when sync-versions moved into the
`version` lifecycle. A comment that describes behaviour the code no longer has is
worse than none, because the next reader trusts it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for freeto 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

@brentrager
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(release): commit synced versions, add drift guard, Go /v2 module path - #66

Merged
brentrager merged 2 commits into
mainfrom
fix/version-sync
Aug 20, 2026
Merged

fix(release): commit synced versions, add drift guard, Go /v2 module path#66
brentrager merged 2 commits into
mainfrom
fix/version-sync

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

Problem

ci:publish was pnpm build && changeset publish && pnpm run version:sync — the sync ran after publish, mutating manifests in a CI workspace that is never committed. Consequences on main today:

fileversionpackage.json
python/pyproject.toml1.1.52.2.12
rust/file/Cargo.toml1.1.5
go/file/version.go1.1.5
dotnet/**/*.csproj2.2.3

git show go/file/v2.2.12:go/file/version.go1.1.5. And cargo publish --allow-dirty existed only to tolerate that dirt.

Separately, Go requires a /vN module-path suffix for major ≥ 2. The module was github.com/SmooAI/file/go/file while the tags were go/file/v2.2.x, so every tagged version resolved nothinggo get only ever got a pseudo-version off main.

Fix

  • Sync moved into the changesets version lifecycle.version: pnpm run version on the action, where pnpm run version = changeset version && node scripts/sync-versions.mjs. The action commits the working tree after version, so the synced manifests land in the release commit. Removed from ci:publish.
  • scripts/version-targets.mjs is now the single list of version-bearing files. sync-versions.mjs writes it; the new scripts/check-versions.mjs (pnpm version:check) asserts it and exits 1 on drift. Wired into PR checks, release, and check-all. Verified by hand-breaking version.go and go.mod locally — red, then green after sync.
  • A pattern matching nothing is now an error in both scripts. It previously printed "Already up to date", which is the fail-open branch and reads as success.
  • rust/file/Cargo.lock is stamped alongside Cargo.toml (name-targeted, so a same-versioned dependency is never touched), so cargo publish runs --locked with no --allow-dirty.
  • Go module path → github.com/SmooAI/file/go/file/v2, derived from package.json's major, guarded by version:check, and re-asserted in the tag step immediately before an immutable tag is pushed. go build/go vet/go test pass. READMEs updated (root, go, python, rust, dotnet) and the "a proper /v2 module path is planned" note removed.

Judgment calls

  • Patch, not major. The Go module path change breaks Go import paths, but the @smooai/file npm API is untouched and changesets versions all five ports off one number — a major would bump to 3.0.0 and immediately require renaming the module again to /v3. No Go consumer can currently be pinned to a tag (none resolve), so pseudo-version users on main are the only ones affected.
  • sync-versions rewrites go.mod's module suffix rather than only warning about it. A bad rewrite fails loudly: go build/go vet resolve the imports in CI.

Follow-up (not in this PR)

After merge + release, mint the tag and verify resolution against the proxy:
GOFLAGS=-mod=mod go get github.com/SmooAI/file/go/file/v2@v2.2.13

🤖 Generated with Claude Code

https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC

…ule path
`version:sync` ran AFTER `changeset publish`, so it mutated manifests in a CI
workspace nobody committed. Every git tag therefore shipped stale version
constants — `go/file/v2.2.12` contains `Version = "1.1.5"` — and
`cargo publish --allow-dirty` existed only to paper over that dirt.
- Move the sync into the changesets `version` lifecycle (`version: pnpm run
version` on the action), where the working tree IS committed into the release
commit, and drop it from `ci:publish`.
- Split the target list into `scripts/version-targets.mjs` so `sync-versions`
writes it and the new `check-versions` guard asserts it. The guard runs in PR
checks, in release, and in `check-all`; it exits 1 on drift. Verified by
hand-breaking `version.go` and `go.mod` locally.
- A pattern that matches nothing is now an error in both scripts. Previously it
was indistinguishable from "already up to date" — the fail-open branch.
- Stamp `rust/file/Cargo.lock`'s own entry alongside `Cargo.toml`, so
`cargo publish` runs `--locked` with no `--allow-dirty`.
- Go requires a `/vN` module suffix for major >= 2, so `go/file/v2.2.x` tags
resolved nothing. Module path is now `github.com/SmooAI/file/go/file/v2`,
derived from `package.json`, guarded by `version:check`, and re-asserted right
before the release tag is pushed. READMEs updated; the "planned /v2" note is
gone because it now exists.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
@changeset-bot

changeset-botBot commented Aug 20, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7dec49d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
NameType
@smooai/filePatch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

…ld catch
`rust/file/src/lib.rs` asserted `VERSION == "1.1.5"` and
`python/tests/test_basic.py` asserted `__version__ == "1.1.5"` — hardcoded
literals kept in step with the stale constants, so both suites went green while
the repo shipped 2.2.12. A test that asserts the constant it guards locks the
drift in instead of catching it. Both now compare against package.json, the
single source of truth `sync-versions.mjs` copies from.
That surfaced one more version-bearing file the sync never covered:
`python/src/smooai_file/__init__.py` hardcodes `__version__`, so the published
wheel reported 1.1.5 while its own pyproject metadata said 2.2.12. Added to
`version-targets.mjs`, which makes it both synced and guarded.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
@brentrager
brentrager merged commit 028ef26 into mainAug 20, 2026
1 check passed
@brentrager
brentrager deleted the fix/version-sync branch August 20, 2026 18:14
brentrager added a commit that referenced this pull request Aug 20, 2026
All three confirmed live in this repo, not theoretical.
1. `go test ./...` served a CACHED pass against a corrupted fixture. Go's build
cache does not invalidate on a file read from outside the package directory —
exactly the shape of the shared contract fixtures in spec/. Verified by
zeroing every sha256 and setting headBytes to 999: still `ok (cached)`. So
the Go quarter of the five-port lazy contract was proving nothing. `go:test`
now passes `-count=1`; the same corruption fails immediately.
2. release.yml ran `pnpm format` in WRITE mode. Whatever it rewrote was either
swept into the release commit unreviewed, or — in publish mode, where the
changesets action commits nothing — left the tree dirty for
`cargo publish --locked`, which would fail every release now that
`--allow-dirty` is gone (dropped in #66). The step is now `format:check`, and
the formatting the release genuinely needs moved into `pnpm run version`,
before the action commits. Confirmed that changesets' generated CHANGELOG.md
is NOT oxfmt-clean, so without that ordering `format:check` would redden
every future release PR.
3. PyPI, crates.io, NuGet and the Go tag were gated on
`steps.changesets.outputs.published == 'true'` — on npm having published in
THAT run. npm succeeds, a later step fails, the retry finds nothing new for
npm, all four skip: a GREEN run that published nothing, leaving four ports on
the old version indefinitely. Each is now gated on whether its own registry
carries package.json's version, so a retry ships exactly what is missing, and
a final step fails the run if npm published a version the others did not.
`scripts/check-registries.mjs` does the detection, waits out index propagation
(reading "missing" during the lag would skip the publish this run just earned,
then report success), and doubles as a manual "are we stranded?" command.
Verified both directions: all five present on 2.2.14 exits 0; npm present with
the Go tag missing exits 1 naming it.
Checked and NOT stranded today: npm, PyPI, crates.io, both NuGet packages and
the Go tag are all on 2.2.14, so the truncation fix reached every port.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
brentrager added a commit that referenced this pull request Aug 20, 2026
* fix(release): three traps that report success while doing nothing
All three confirmed live in this repo, not theoretical.
1. `go test ./...` served a CACHED pass against a corrupted fixture. Go's build
cache does not invalidate on a file read from outside the package directory —
exactly the shape of the shared contract fixtures in spec/. Verified by
zeroing every sha256 and setting headBytes to 999: still `ok (cached)`. So
the Go quarter of the five-port lazy contract was proving nothing. `go:test`
now passes `-count=1`; the same corruption fails immediately.
2. release.yml ran `pnpm format` in WRITE mode. Whatever it rewrote was either
swept into the release commit unreviewed, or — in publish mode, where the
changesets action commits nothing — left the tree dirty for
`cargo publish --locked`, which would fail every release now that
`--allow-dirty` is gone (dropped in #66). The step is now `format:check`, and
the formatting the release genuinely needs moved into `pnpm run version`,
before the action commits. Confirmed that changesets' generated CHANGELOG.md
is NOT oxfmt-clean, so without that ordering `format:check` would redden
every future release PR.
3. PyPI, crates.io, NuGet and the Go tag were gated on
`steps.changesets.outputs.published == 'true'` — on npm having published in
THAT run. npm succeeds, a later step fails, the retry finds nothing new for
npm, all four skip: a GREEN run that published nothing, leaving four ports on
the old version indefinitely. Each is now gated on whether its own registry
carries package.json's version, so a retry ships exactly what is missing, and
a final step fails the run if npm published a version the others did not.
`scripts/check-registries.mjs` does the detection, waits out index propagation
(reading "missing" during the lag would skip the publish this run just earned,
then report success), and doubles as a manual "are we stranded?" command.
Verified both directions: all five present on 2.2.14 exits 0; npm present with
the Go tag missing exits 1 naming it.
Checked and NOT stranded today: npm, PyPI, crates.io, both NuGet packages and
the Go tag are all on 2.2.14, so the truncation fix reached every port.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
* fix(release): close a fail-open in the registry check, and stop it crying wolf on NuGet
Two corrections found while verifying the previous commit rather than assuming it.
A botched edit had left the npm-probe result being overwritten by a later branch,
so `--expect-npm` returned exit 0 on a version npm did not have. Every downstream
gate is `!has && present.npm`, so a false npm reading would have switched all four
publishes off and let the run go green having shipped nothing — the exact
fail-open this script exists to remove, reintroduced one level up. Now exits 1
with a message naming the disagreement. Verified: exit 1 on an unpublished
version, exit 0 once npm has it.
NuGet is no longer asserted on. Its index takes minutes to tens of minutes to
show a package it has already accepted — 2.2.19 logged "Your package was pushed"
for both packages and the flat-container index still read 2.2.14 twenty minutes
later — so the guard would have reddened every successful release, and a guard
that cries wolf gets deleted. npm, PyPI, crates.io and the Go tag index in
seconds and stay strict. NuGet keeps its own protection: `dotnet nuget push`
exits non-zero on a real failure, and the per-registry gate skips it only when
the version is genuinely already there. Its state is still reported, just not
enforced, and the reason is in the code so it does not read as an oversight.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
* docs(release): correct two workflow comments my own changes made stale
The stranding guard's comment still said "any of the other four" after NuGet was
dropped from the strict set, and the PyPI step still called the wheels it cleans
"pre-sync version" — which stopped being true when sync-versions moved into the
`version` lifecycle. A comment that describes behaviour the code no longer has is
worse than none, because the next reader trusts it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0152bbE1veqfG1SVJdyLCBxC
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for freeto 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

@brentrager