Skip to content

Publish HyperMeow as its own Go module - #33

Merged
purpshell merged 9 commits into
devfrom
codex/module-path-hypermeow
Aug 10, 2026
Merged

Publish HyperMeow as its own Go module#33
purpshell merged 9 commits into
devfrom
codex/module-path-hypermeow

Conversation

@purpshell

@purpshell purpshell commented Aug 10, 2026

Copy link
Copy Markdown
Member

HyperMeow declared module go.mau.fi/whatsmeow, so the only way to consume it was replace go.mau.fi/whatsmeow => github.com/polymorfa/hypermeow.

Go honours a replace only in the main module. That meant the arrangement never propagated: anything depending in turn on a module that used HyperMeow silently resolved upstream whatsmeow instead, and go get github.com/polymorfa/hypermeow could not work at all.

This declares the module under its own path and rewrites internal import paths to match.

Scope

  • go.mod: module github.com/polymorfa/hypermeow
  • 130 Go sources + 57 .proto go_package options rewritten
  • 57 generated .pb.go regenerated (see below)
  • benchmark/barback (already named github.com/polymorfa/hypermeow/benchmark/barback) now requires the parent directly
  • .pre-commit-config.yaml goimports -local prefix, Dockerfiles, README

Package names are unchanged - the root package is still whatsmeow, so only import paths move and no call site changes:

import whatsmeow "github.com/polymorfa/hypermeow"

The generated descriptors were regenerated, not text-edited

This is the part worth reviewing closely. go_package lives in the raw descriptor behind two protobuf varint length prefixes:

"\x06IGNORE\x10\x02B$Z\"go.mau.fi/whatsmeow/proto/waCommon"
                    ^ ^
                    | +-- 0x22 = 34 = len(path)
                    +---- 0x24 = 36 = len(options)

The path grows by 11 bytes (go.mau.fi/whatsmeow 19 -> github.com/polymorfa/hypermeow 30), so a sed would have left both prefixes understating their payload and corrupted all 57 descriptors at runtime. Regenerated output confirms it:

"\x06IGNORE\x10\x02B/Z-github.com/polymorfa/hypermeow/proto/waCommon"
                    ^ ^
                    | +-- 0x2D = 45 = 34 + 11
                    +---- 0x2F = 47 = 36 + 11

Regeneration used the toolchain the committed artifacts were produced by (protoc-gen-go v1.36.11), verified beforehand by regenerating an unmodified proto and confirming byte-identical output. The only incidental diff is the recorded protoc patch version, v6.33.5 -> v6.33.6 (protobuf 33.5 -> 33.6, same minor series). Net: 57 files, 201 insertions / 201 deletions.

Verification

go build ./..., go vet ./..., go test ./... (8 packages, 0 failures), go test -race ./... (8 packages, 0 failures), nested benchmark module builds, gofmt clean, goimports -local github.com/polymorfa/hypermeow clean, and the CI MEX consistency check (genmex -check) passes.

Coordination

This is a breaking change for consumers. Titan currently pins the old path via replace and must be updated in lockstep - Go rejects a replace whose target declares a different module path. The matching Titan change is next.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

HyperMeow declared `module go.mau.fi/whatsmeow`, so consumers could only
reach it through `replace go.mau.fi/whatsmeow => github.com/polymorfa/
hypermeow`. Go only honours a `replace` in the main module, so that
arrangement never propagated: anything that depended in turn on a module
using HyperMeow silently resolved upstream whatsmeow instead, and
`go get github.com/polymorfa/hypermeow` could not work at all.

Declare the module under its own path, `github.com/polymorfa/hypermeow`,
and rewrite internal import paths to match. Package names are unchanged
- the root package is still `whatsmeow` - so only import paths move and
no call site changes.

The 57 generated descriptors are regenerated rather than text-edited.
The `go_package` option is stored in the raw descriptor behind two
protobuf varint length prefixes, and the path grows by 11 bytes, so a
substitution would have left every length prefix understating its
payload and corrupted the descriptors at runtime. Regenerating with the
toolchain the artifacts were produced by (protoc-gen-go v1.36.11)
reproduces them byte-identically apart from the intended change; the
only incidental diff is the recorded protoc patch version, 6.33.5 to
6.33.6.

The nested benchmark module already carried the new path and now
requires it directly.
@purpshell

Copy link
Copy Markdown
Member Author

@codex review

Please review exact head b5e5ba37e07593ec608787733f0bc2664afe61cd against its current base (dev).

This renames the module from go.mau.fi/whatsmeow to its own path github.com/polymorfa/hypermeow, removing the need for a replace directive.

Highest-value areas to challenge:

  1. Generated descriptors. The 57 .pb.go were regenerated, not text-edited, because go_package sits behind two protobuf varint length prefixes and the path grows 11 bytes. Please confirm every regenerated descriptor is internally consistent and that no length prefix was left stale. I verified the toolchain first by regenerating an unmodified proto and confirming byte-identical output.
  2. Completeness. Any remaining go.mau.fi/whatsmeow reference that should have moved, or any that should have stayed (upstream attribution in README is intentional).
  3. go.mau.fi/util must not have been rewritten — it is a separate module.
  4. The nested benchmark/barback module requirement and replace target.
  5. Whether keeping the root package name whatsmeow (rather than renaming to hypermeow) creates any hazard for consumers.

Local controls on this head: go build ./..., go vet ./..., go test ./... and go test -race ./... (8 packages, 0 failures), nested benchmark module builds, gofmt/goimports clean, genmex -check passes.

@purpshell

Copy link
Copy Markdown
Member Author

@coderabbitai review

Please review exact head b5e5ba37e07593ec608787733f0bc2664afe61cd against its current base (dev).

Module path rename from go.mau.fi/whatsmeow to github.com/polymorfa/hypermeow, eliminating the replace-directive requirement for consumers.

Please pay particular attention to:

  1. Correctness and internal consistency of the 57 regenerated protobuf descriptors (the go_package string is length-prefixed; a naive substitution would corrupt them).
  2. Any missed or over-eager path rewrite — go.mau.fi/util is a different module and must be untouched.
  3. go.mod / nested benchmark/barback/go.mod require and replace correctness.
  4. Whether the README migration guidance is accurate for downstream consumers.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep them coming!

Reviewed commit: b5e5ba37e0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

godoc renders the root package as "whatsmeow package -
github.com/polymorfa/hypermeow", which reads like the rename did not
take. It did: only the module path moved. Say so directly, and give the
reason the package clause stays on the upstream name.
@purpshell

Copy link
Copy Markdown
Member Author

@codex review

Head has moved to 7d1e837a27b0229fc8a6d642dcd8820e26f60aae (adds a README section explaining that the module is hypermeow while the root package clause stays whatsmeow). Please review this exact head against dev; the earlier request at b5e5ba3 is superseded.

@purpshell

Copy link
Copy Markdown
Member Author

@coderabbitai review

Head has moved to 7d1e837a27b0229fc8a6d642dcd8820e26f60aae (README-only change on top of the module rename). Please review this exact head against dev.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7d1e837a27

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread go.mod
A distinct module path lets Go compile HyperMeow alongside upstream
whatsmeow. Both keep upstream's generated descriptor paths, so the
process-global protobuf registry panics before main. The replace
directive made that impossible; document the constraint and how to
assert it at build time.
@purpshell

Copy link
Copy Markdown
Member Author

@coderabbitai review

Please review exact head 3ea2d78aab3d1b8045d484d6510c194b625faacd against dev. Adds documentation of the single-whatsmeow-per-binary constraint introduced by giving the fork its own module path.

@purpshell

Copy link
Copy Markdown
Member Author

Thanks — that P1 is correct and I have addressed it in 3ea2d78aab3d1b8045d484d6510c194b625faacd.

You are right that this is a real regression in kind, not just degree: under replace both import paths resolved to a single module, so coexistence was structurally impossible. A distinct module path removes that guarantee, and the shared descriptor paths then panic the global registry before main.

On the proposed remedy — giving the fork a distinct protobuf descriptor namespace — I looked at it and did not take it, for reasons I want to state rather than silently decline:

  • Renaming the descriptor file paths alone would not fix it. protoimpl also registers every message and enum by full name, so WACommon.MessageKey and friends would still collide. A complete fix means changing the proto package declarations too.
  • Those declarations are Meta's, not ours. Changing them alters Any type URLs and protobuf-JSON names, which are part of how this library talks to WhatsApp. Trading a loud, immediate init panic for a possible silent wire/JSON divergence against a live protocol is a bad exchange, and it would make every future merge from tulir conflict across all 57 descriptors.

So I treated it as a constraint to enforce rather than design away, given the failure is fail-loud and fully diagnosable:

  1. Verified the constraint currently holds. go list -m all and go mod graph show go.mau.fi/whatsmeow absent from both this module's graph and Titan's — no partially migrated graph exists in practice.
  2. Moved detection from process start to CI. Titan gains a debug.ReadBuildInfo() assertion that fails if any dependency reports the path go.mau.fi/whatsmeow (titan-api/titan#56, 016798ef). That converts the panic-before-main into a failing test.
  3. Documented the constraint in the README with the exact panic text and both checks (go mod why, or the build-info assertion).

If you still consider a distinct descriptor namespace necessary despite the wire-compatibility cost, say so and I will treat it as blocking and scope it separately — I did not want to make that trade unilaterally.

@codex review

Please review exact head 3ea2d78aab3d1b8045d484d6510c194b625faacd against dev.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3ea2d78aab

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread README.md Outdated
Without -m the query is about the package, so a dependency importing
only a subpackage such as proto/waCommon reports that the module is not
needed while it is in fact linked. Add a link-graph check as well.
@purpshell

Copy link
Copy Markdown
Member Author

Second P1 is correct — fixed in 6eaf52956e918a3d5ea558c2edebee5a260899db.

You are right that go mod why go.mau.fi/whatsmeow asks about the package, so a dependency importing only go.mau.fi/whatsmeow/proto/waCommon gets "main module does not need package" while the module is linked and its descriptors still collide. The README now uses go mod why -m, explains why the -m matters, and adds a stricter link-graph check (go list -deps ./... | grep '^go\.mau\.fi/whatsmeow') alongside the debug.ReadBuildInfo() assertion.

@codex review

Please review exact head 6eaf52956e918a3d5ea558c2edebee5a260899db against dev.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6eaf52956e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread README.md Outdated
grep exits 0 on a match, so the documented command succeeded precisely
when upstream whatsmeow was in the graph.
@purpshell

Copy link
Copy Markdown
Member Author

P2 fixed in 92b49be — you were right, and it was the more dangerous kind of wrong.

The documented check had its exit status backwards: grep exits 0 on a match, so as written the step would have succeeded exactly when upstream whatsmeow was in the graph and failed when it was clean. A CI job using it would have reported green on precisely the unsafe condition it existed to catch. Negated, and I noted why the ! is load-bearing so it survives future edits.

Verified empirically: ! go list -deps ./... | grep -q "^go\.mau\.fi/whatsmeow" now exits 0 on this module (safe). The same inversion was present in the Titan-side check and is fixed there too (87de98c), where it correctly exits 1 — because that package genuinely does link libsignal today.

I have resolved the go.mod coexistence thread, since it is addressed as far as this PR can address it: verified absent from both graphs, enforced by a debug.ReadBuildInfo() assertion in Titan CI, and documented with both checks. My reasoning for not renaming the descriptor namespace is in the comment above — reopen if you disagree.

@codex review

Please review exact head 92b49bebe6d69e6a58f4d2fdd8e88b95f47b0bdc against dev.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 92b49be16d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread README.md Outdated
@purpshell

Copy link
Copy Markdown
Member Author

Applied the same correction in b86af76: go list is now kept out of the negated pipeline, so a graph that fails to load cannot read as clean. Verified in the Titan repo in both directions (real graph and deliberately broken graph both exit nonzero rather than reporting safe).

@codex review

Please review exact head b86af76 against dev.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b86af7604f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread README.md Outdated
Comment thread README.md Outdated
@purpshell

Copy link
Copy Markdown
Member Author

Both P2s fixed in 45788c7.

Test-only deps: correct — go list -deps omits them, so a project importing upstream from a _test.go would pass while go test links both copies. Now uses -test.

POSIX portability: also correct — <<< is bash-only and would have died with Syntax error: redirection unexpected under dash, which for an sh-fenced snippet is exactly the wrong failure mode. Replaced with printf.

Verified under a real /bin/sh: exits 0 on this module (safe), and a deliberately broken graph exits nonzero rather than reporting clean.

@codex review

Please review exact head 45788c7 against dev.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Already looking forward to the next diff.

Reviewed commit: 45788c789f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@purpshell

Copy link
Copy Markdown
Member Author

Same simplification in 2a5d862 — the coexistence check now captures go list output and matches with a case builtin, so neither a failed go list nor a missing grep can invert into a pass. Verified exit 0 on this module under POSIX sh.

On the recurring go.mod P1: I am treating it as addressed rather than open, and want the reasoning on record rather than silently dismissing it.

The finding is accurate — a distinct module path does allow both copies to link, and the descriptors do collide. But the proposed remedy, a distinct protobuf descriptor namespace, is not one I will take unilaterally: it requires changing Meta protobuf package declarations, which alters Any type URLs and protobuf-JSON names against a live protocol, and would conflict every future merge from tulir across all 57 descriptors. Trading a loud, immediate, fully diagnosable init panic for possible silent wire divergence is the wrong direction.

What I have done instead is make the unsafe state impossible to reach unnoticed:

  1. verified go.mau.fi/whatsmeow is absent from this module's graph and Titan's;
  2. added a debug.ReadBuildInfo() assertion in Titan CI that fails if any dependency reports that module path;
  3. documented the constraint with a check that cannot report clean without seeing a clean graph.

If a maintainer judges the namespace split necessary despite the wire-compatibility cost, I will scope it as its own change — but it should not ride along inside a module-path migration.

@codex review

Please review exact head 2a5d862 against dev.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Breezy!

Reviewed commit: 2a5d862c0d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

The go-mod-tidy pre-commit hook fails on nine stale entries for modules
no longer in the graph.
@purpshell

Copy link
Copy Markdown
Member Author

Resolving the remaining README threads. All of them were about successive edge cases in one shell snippet — inverted grep exit status, a failed go list inverting into a pass, missing -test, a bash-only here-string, and grep itself erroring. Each was correct, and together they showed the shape was wrong rather than the details, so the snippet no longer uses a pipeline or any external command:

deps="$(go list -deps -test ./...)" || exit 1
case "$deps" in *go.mau.fi/whatsmeow*) exit 1 ;; esac

go list failure propagates through ||; the match is a shell builtin with no independent failure mode; -test covers test-only imports. Verified exit 0 on this module under a real POSIX /bin/sh.

CI is green on both Go 1.25 and 1.26 at aeb9eb213ef81efb8d9cba83b14d21032d2d7b6c (the last red run was a stale go.sum, now tidied).

@purpshell
purpshell merged commit 9da6911 into dev Aug 10, 2026
5 checks passed
@purpshell
purpshell deleted the codex/module-path-hypermeow branch August 10, 2026 23:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant