Why must the module path declared in go.mod match the repository URL consumers import from?
answer
- the path is also the lookup key
- no registry to ask, so derive it
- checked again after download
- declared path versus required path
- meta tag for a vanity domain
basics
~20 sThe module path is the module's identity and its fetch address at once: the go command turns the import path into a repository location, then checks the downloaded go.mod declares that same path. A mismatch fails the build.
solid answer
~50 sA Go import path is not just a name, it is the lookup key. When someone imports `github.com/acme/logkit/logfmt`, the go command strips the path back to find the module, resolves that prefix to a repository, downloads the tagged tree, and then reads the `module` line inside the fetched `go.mod`. If that line says something else, the fetch fails with `module declares its path as: ... but was required as: ...`. That keeps a module's identity stable no matter which mirror served the bytes. So the `module` line must be the path you want people to type, and it must resolve: on the well-known Git hosts that is the repository URL without the scheme; on your own domain you serve an HTML page carrying a `go-import` meta tag pointing at the real repository. Renaming a repository therefore changes the module's identity, not just its address.
code
text · 4 lines$ go get example.com/[email protected]
go: example.com/[email protected]: parsing go.mod:
module declares its path as: github.com/acme/logkit
but was required as: example.com/logkitgo deeper
Be able to state that the module line must be the exact path other people import, normally the repository URL without the scheme, and that guessing a short name breaks anyone who tries to fetch it.
Explain the round trip: shortest-prefix resolution, the built-in mapping for common hosts or the go-import meta tag for a custom domain, the download, then the re-check of the declared module line and the error text it produces.
Show that you can tell the failure modes apart under pressure — a declared-path mismatch after a rename, an authentication failure on a private repository, and a vanity domain that stopped serving its meta tag all look different and have different fixes.
Own the path choice before the first release: a vanity domain buys host portability but commits the organisation to keeping that endpoint alive for years, while a host path is free and permanent. Publishing makes the decision irreversible for every consumer.
## The path is the identity and the address The `module` line in `go.mod` does double duty. It is the **name** — the prefix of every import path in the module — and it is the **address**, because the go command has no registry to ask and must derive a repository location from the path itself. Those two roles have to agree, and the toolchain enforces the agreement rather than trusting it. ### How a path becomes a repository Given `import github.com/acme/logkit/logfmt`, the go command does not know where the module boundary is, so it tries successively shorter prefixes until one resolves to a module: `github.com/acme/logkit/logfmt`, then `github.com/acme/logkit`, and so on. For the well-known code hosts the mapping is built in — the first two path elements after the host are the repository. For any other domain, the go command makes an HTTPS request to `https://<path>?go-get=1` and looks for an HTML meta tag: ```html <meta name="go-import" content="example.com/logkit git https://github.com/acme/logkit"> ``` That is the vanity-import mechanism: the import path is a name you control, and the meta tag says where the code actually lives. It is why a module can be imported as `example.com/logkit` while being hosted anywhere. ### The check on the way back Resolution gets bytes. The go command then opens the fetched `go.mod` and compares its `module` line with the path that was requested. Disagreement is a hard error: ``` go: example.com/[email protected]: parsing go.mod: module declares its path as: github.com/acme/logkit but was required as: example.com/logkit ``` The check matters because content can arrive through a mirror or a cache rather than straight from the repository. Verifying the declared path means the module cannot be silently served under someone else's name, and it means the same import path always denotes the same module regardless of the route the bytes took. ### The consequences maintainers actually feel **Renaming the repository renames the module.** If `github.com/acme/logkit` becomes `github.com/acme/logging`, the old import path no longer names your module. Even where the host issues an HTTP redirect and the fetch follows it, the `go.mod` at the other end now declares the new path, and consumers get the mismatch error. A rename is therefore a coordinated change: update the `module` line, update every internal import, tag a new version, and every consumer edits their imports. Treat it as publishing a different module, because that is what it is. **Moving a module to a subdirectory renames it too**, since the path includes the directory prefix under the repository root. **A private repository is a permissions problem, not a path problem.** The path still resolves; the fetch is what fails. The declared-path error and an authentication error look nothing alike, which is a useful diagnostic split. **The path must be a real, resolvable domain.** Inventing `module mycompany/tools` or `module logkit` produces something no consumer can fetch, and the failure only appears when a second team tries to import it. It is fine for a program nobody imports; it is a bug the moment the module is published. ### Choosing the path deliberately Because the path is baked into every consumer's imports, it is close to unchangeable once published. A vanity domain (`go.acme.dev/logkit`) buys the freedom to move hosts later without touching a single consumer import, at the cost of running a small static site that must stay up for as long as the module is used — a real operational commitment, since the go command consults it on a cold fetch. Using the host path directly costs nothing and ties you to that host's URL forever. Either is defensible; deciding after publication is not.
- Your organisation renames the GitHub repository behind a published module. What has to happen?Treat it as a new module. Update the `module` line to the new path, rewrite the module's own imports, tag a release, and expect every consumer to change their import statements and require line. Host redirects do not save you: the fetch follows the redirect and then fails the declared-path check, because the go.mod at the destination names the new path.
- How can a module be imported as example.com/logkit while the code lives on a third-party Git host?Serve an HTML page at that URL containing `<meta name="go-import" content="example.com/logkit git https://github.com/acme/logkit">`. The go command requests the path with `?go-get=1` and follows the tag to the real repository. The go.mod must then declare `module example.com/logkit`, since that is the name consumers require.
- Why check the declared path again after downloading, when resolution already found the repository?Because the bytes need not have come from the repository — a mirror, cache or corporate proxy may have served them. Re-reading the module line makes the identity a property of the content rather than of the route, so one import path can never quietly resolve to a different module's code.
saying these in an interview costs you the question
- Treats the module line as a free-form project name
- Assumes a host redirect makes a rename transparent
- Thinks go get needs a full URL with https
- Believes a registry maps names to repositories
- Uses a bare name like myapp for a published module